diff --git a/forms/projectsettingseditor.ui b/forms/projectsettingseditor.ui index 02ecf34c..26813862 100644 --- a/forms/projectsettingseditor.ui +++ b/forms/projectsettingseditor.ui @@ -21,7 +21,7 @@ - 1 + 0 @@ -370,7 +370,7 @@ 0 0 531 - 545 + 587 @@ -603,20 +603,6 @@ - - - - Collision - - - - - - - Elevation - - - @@ -624,6 +610,13 @@ + + + + Collision + + + @@ -631,6 +624,13 @@ + + + + Elevation + + + @@ -638,6 +638,19 @@ + + + + color : red; + + + These masks have overlapping bits. This may result in unexpected value changes. + + + true + + + @@ -731,7 +744,7 @@ 0 0 528 - 522 + 568 @@ -799,10 +812,17 @@ Metatiles - - - - Layer Type mask + + + + The mask used to read/write Layer Type from the metatile's attributes data. If 0, this attribute is disabled. + + + + + + + The mask used to read/write Metatile Behavior from the metatile's attributes data. If 0, this attribute is disabled. @@ -816,24 +836,33 @@ - - - - false + + + + Qt::Vertical - - - - - - The mask used to read/write Encounter Type from the metatile's attributes data. If 0, this attribute is disabled. + + QSizePolicy::Maximum - + + + 20 + 10 + + + - - + + - Behavior mask + Encounter Type mask + + + + + + + Enable Triple Layer Metatiles @@ -844,18 +873,46 @@ - - - - Qt::Vertical + + + + Behavior mask - - - 20 - 15 - + + + + + + color : red; - + + These masks have overlapping bits. This may result in unexpected value changes. + + + true + + + + + + + The mask used to read/write Encounter Type from the metatile's attributes data. If 0, this attribute is disabled. + + + + + + + Layer Type mask + + + + + + + false + + @@ -864,33 +921,21 @@ - - - - Encounter Type mask + + + + Qt::Vertical - - - - - - The mask used to read/write Metatile Behavior from the metatile's attributes data. If 0, this attribute is disabled. + + QSizePolicy::MinimumExpanding - - - - - - The mask used to read/write Layer Type from the metatile's attributes data. If 0, this attribute is disabled. + + + 20 + 1 + - - - - - - Enable Triple Layer Metatiles - - + diff --git a/include/config.h b/include/config.h index 7605f0d6..1a6e0ae8 100644 --- a/include/config.h +++ b/include/config.h @@ -286,10 +286,10 @@ public: int getNumTilesInMetatile(); void setDefaultMetatileId(uint16_t metatileId); uint16_t getDefaultMetatileId(); - void setDefaultElevation(int elevation); - int getDefaultElevation(); - void setDefaultCollision(int collision); - int getDefaultCollision(); + void setDefaultElevation(uint16_t elevation); + uint16_t getDefaultElevation(); + void setDefaultCollision(uint16_t collision); + uint16_t getDefaultCollision(); void setNewMapBorderMetatileIds(QList metatileIds); QList getNewMapBorderMetatileIds(); QString getDefaultPrimaryTileset(); diff --git a/include/core/bitpacker.h b/include/core/bitpacker.h index e6f6e845..4e3767e4 100644 --- a/include/core/bitpacker.h +++ b/include/core/bitpacker.h @@ -2,7 +2,6 @@ #define BITPACKER_H #include -//#include class BitPacker { diff --git a/include/core/metatile.h b/include/core/metatile.h index 5ef86a3e..1570e350 100644 --- a/include/core/metatile.h +++ b/include/core/metatile.h @@ -11,7 +11,6 @@ class Project; -// TODO: Reevaluate enums enum { METATILE_LAYER_MIDDLE_TOP, METATILE_LAYER_BOTTOM_MIDDLE, @@ -72,6 +71,7 @@ public: static int getIndexInTileset(int); static QPoint coordFromPixmapCoord(const QPointF &pixelCoord); static uint32_t getDefaultAttributesMask(BaseGameVersion version, Metatile::Attr attr); + static uint32_t getMaxAttributesMask(); static int getDefaultAttributesSize(BaseGameVersion version); static void setLayout(Project*); static QString getMetatileIdString(uint16_t metatileId) { diff --git a/include/mainwindow.h b/include/mainwindow.h index 9c9d270b..6c534dee 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -372,7 +372,7 @@ private: void initMapSortOrder(); void initShortcuts(); void initExtraShortcuts(); - bool setProjectSpecificUI(); + void setProjectSpecificUI(); void setWildEncountersUIEnabled(bool enabled); void loadUserSettings(); void applyMapListFilter(QString filterText); diff --git a/include/project.h b/include/project.h index ee727f07..2428aa89 100644 --- a/include/project.h +++ b/include/project.h @@ -216,6 +216,7 @@ public: QString getDefaultSecondaryTilesetLabel(); void setImportExportPath(QString filename); + void applyParsedLimits(); static int getNumTilesPrimary(); static int getNumTilesTotal(); diff --git a/include/ui/projectsettingseditor.h b/include/ui/projectsettingseditor.h index 2510e788..75843886 100644 --- a/include/ui/projectsettingseditor.h +++ b/include/ui/projectsettingseditor.h @@ -3,6 +3,7 @@ #include #include "project.h" +#include "ui_projectsettingseditor.h" class NoScrollComboBox; class QAbstractButton; @@ -55,6 +56,7 @@ private: void chooseFile(QLineEdit * filepathEdit, const QString &description, const QString &extensions); QString stripProjectDir(QString s); void disableParsedSetting(QWidget * widget, const QString &name, const QString &filepath); + void updateMaskOverlapWarning(QLabel * warning, QList masks); private slots: void dialogButtonClicked(QAbstractButton *button); @@ -63,6 +65,8 @@ private slots: void updatePokemonIconPath(const QString &species); void markEdited(); void on_mainTabs_tabBarClicked(int index); + void updateBlockMaskOverlapWarning(); + void updateAttributeMaskOverlapWarning(); }; #endif // PROJECTSETTINGSEDITOR_H diff --git a/src/config.cpp b/src/config.cpp index ceb7ad01..48bb9e95 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -1043,21 +1043,21 @@ uint16_t ProjectConfig::getDefaultMetatileId() { return this->defaultMetatileId; } -void ProjectConfig::setDefaultElevation(int elevation) { +void ProjectConfig::setDefaultElevation(uint16_t elevation) { this->defaultElevation = elevation; this->save(); } -int ProjectConfig::getDefaultElevation() { +uint16_t ProjectConfig::getDefaultElevation() { return this->defaultElevation; } -void ProjectConfig::setDefaultCollision(int collision) { +void ProjectConfig::setDefaultCollision(uint16_t collision) { this->defaultCollision = collision; this->save(); } -int ProjectConfig::getDefaultCollision() { +uint16_t ProjectConfig::getDefaultCollision() { return this->defaultCollision; } diff --git a/src/core/bitpacker.cpp b/src/core/bitpacker.cpp index dd8815bb..f44d8140 100644 --- a/src/core/bitpacker.cpp +++ b/src/core/bitpacker.cpp @@ -21,7 +21,7 @@ void BitPacker::setMask(uint32_t mask) { m_maxValue = (m_setBits.length() >= 32) ? UINT_MAX : ((1 << m_setBits.length()) - 1); } -// Given an arbitrary value to set for this bitfield member, returns a bounded value that can later be packed losslessly. +// Given an arbitrary value to set for this bitfield member, returns a (potentially truncated) value that can later be packed losslessly. uint32_t BitPacker::clamp(uint32_t value) const { return (m_maxValue == UINT_MAX) ? value : (value % (m_maxValue + 1)); } diff --git a/src/core/block.cpp b/src/core/block.cpp index 1ef1c703..9eab9d13 100644 --- a/src/core/block.cpp +++ b/src/core/block.cpp @@ -12,7 +12,7 @@ static BitPacker bitsElevation = BitPacker(0xF000); Block::Block() : m_metatileId(0), m_collision(0), - m_elevation(0) + m_elevation(0) { } Block::Block(uint16_t metatileId, uint16_t collision, uint16_t elevation) : @@ -46,32 +46,10 @@ uint16_t Block::rawValue() const { | bitsElevation.pack(m_elevation); } -// TODO: After parsing, recalc config (or parsed!) values that depend on max collision/elevation -/* - newMapMetatileId - - newMapElevation - - newMapCollision - - newMapBorderMetatileIds - - collisionSheetWidth - - collisionSheetHeight - - NUM_METATILES_IN_PRIMARY - - event elevations - - metatile labels? - -*/ -// TODO: Settings editor -- disable UI & restore after refresh void Block::setLayout() { bitsMetatileId.setMask(projectConfig.getBlockMetatileIdMask()); bitsCollision.setMask(projectConfig.getBlockCollisionMask()); bitsElevation.setMask(projectConfig.getBlockElevationMask()); - - // Some settings may need to be reevaluated based on the layout - /*uint16_t metatileId = projectConfig.getNewMapMetatileId(); - if (bitsMetatileId.clamp(metatileId) != metatileId) - projectConfig.setNewMapMetatileId(bitsMetatileId.clamp(metatileId)); - uint16_t metatileId = projectConfig.getNewMapMetatileId(); - if (bitsMetatileId.clamp(metatileId) != metatileId) - projectConfig.setNewMapMetatileId(bitsMetatileId.clamp(metatileId));*/ - } bool Block::operator ==(Block other) const { diff --git a/src/core/metatile.cpp b/src/core/metatile.cpp index 937db58f..565dd8f7 100644 --- a/src/core/metatile.cpp +++ b/src/core/metatile.cpp @@ -82,73 +82,66 @@ uint32_t Metatile::getDefaultAttributesMask(BaseGameVersion version, Metatile::A return vanillaPackers.value(attr).mask(); } -bool Metatile::doMasksOverlap(QList masks) { - for (int i = 0; i < masks.length(); i++) - for (int j = i + 1; j < masks.length(); j++) { - if (masks.at(i) & masks.at(j)) - return true; - } - return false; -} - -void Metatile::setLayout(Project * project) { - // Read masks from the config and limit them based on the specified attribute size. - const QHash maxMasks = { +uint32_t Metatile::getMaxAttributesMask() { + static const QHash maxMasks = { {1, 0xFF}, {2, 0xFFFF}, {4, 0xFFFFFFFF}, }; - uint32_t maxMask = maxMasks.value(projectConfig.getMetatileAttributesSize(), 0); - uint32_t behaviorMask = projectConfig.getMetatileBehaviorMask() & maxMask; - uint32_t terrainTypeMask = projectConfig.getMetatileTerrainTypeMask() & maxMask; - uint32_t encounterTypeMask = projectConfig.getMetatileEncounterTypeMask() & maxMask; - uint32_t layerTypeMask = projectConfig.getMetatileLayerTypeMask() & maxMask; - - // TODO: Overlap handling to settings editor; set red text box with similar text warning if overlapping - // Overlapping masks are technically ok, but probably not intended. - // Additionally, Porymap will not properly reflect that the values are linked. - if (doMasksOverlap({behaviorMask, terrainTypeMask, encounterTypeMask, layerTypeMask})) { - logWarn("Metatile attribute masks are overlapping. This may result in unexpected attribute values."); - } + return maxMasks.value(projectConfig.getMetatileAttributesSize(), 0); +} +void Metatile::setLayout(Project * project) { + uint32_t behaviorMask = projectConfig.getMetatileBehaviorMask(); + uint32_t terrainTypeMask = projectConfig.getMetatileTerrainTypeMask(); + uint32_t encounterTypeMask = projectConfig.getMetatileEncounterTypeMask(); + uint32_t layerTypeMask = projectConfig.getMetatileLayerTypeMask(); // Calculate mask of bits not used by standard behaviors so we can preserve this data. uint32_t unusedMask = ~(behaviorMask | terrainTypeMask | encounterTypeMask | layerTypeMask); - unusedMask &= maxMask; + unusedMask &= Metatile::getMaxAttributesMask(); BitPacker packer = BitPacker(unusedMask); attributePackers.clear(); attributePackers.insert(Metatile::Attr::Unused, unusedMask); - // TODO: Test displaying 32 bit behavior - // TODO: Logging masks to hex // Validate metatile behavior mask packer.setMask(behaviorMask); if (behaviorMask && !project->metatileBehaviorMapInverse.isEmpty()) { uint32_t maxBehavior = project->metatileBehaviorMapInverse.lastKey(); if (packer.clamp(maxBehavior) != maxBehavior) - logWarn(QString("Metatile Behavior mask '%1' is insufficient to contain all available options.").arg(behaviorMask)); + logWarn(QString("Metatile Behavior mask '0x%1' is insufficient to contain all available options.") + .arg(QString::number(behaviorMask, 16).toUpper())); } attributePackers.insert(Metatile::Attr::Behavior, packer); // Validate terrain type mask packer.setMask(terrainTypeMask); const uint32_t maxTerrainType = NUM_METATILE_TERRAIN_TYPES - 1; - if (terrainTypeMask && packer.clamp(maxTerrainType) != maxTerrainType) - logWarn(QString("Metatile Terrain Type mask '%1' is insufficient to contain all %2 available options.").arg(terrainTypeMask).arg(maxTerrainType + 1)); + if (terrainTypeMask && packer.clamp(maxTerrainType) != maxTerrainType) { + logWarn(QString("Metatile Terrain Type mask '0x%1' is insufficient to contain all %2 available options.") + .arg(QString::number(terrainTypeMask, 16).toUpper()) + .arg(maxTerrainType + 1)); + } attributePackers.insert(Metatile::Attr::TerrainType, packer); // Validate encounter type mask packer.setMask(encounterTypeMask); const uint32_t maxEncounterType = NUM_METATILE_ENCOUNTER_TYPES - 1; - if (encounterTypeMask && packer.clamp(maxEncounterType) != maxEncounterType) - logWarn(QString("Metatile Encounter Type mask '%1' is insufficient to contain all %2 available options.").arg(encounterTypeMask).arg(maxEncounterType + 1)); + if (encounterTypeMask && packer.clamp(maxEncounterType) != maxEncounterType) { + logWarn(QString("Metatile Encounter Type mask '0x%1' is insufficient to contain all %2 available options.") + .arg(QString::number(encounterTypeMask, 16).toUpper()) + .arg(maxEncounterType + 1)); + } attributePackers.insert(Metatile::Attr::EncounterType, packer); // Validate terrain type mask packer.setMask(layerTypeMask); const uint32_t maxLayerType = NUM_METATILE_LAYER_TYPES - 1; - if (layerTypeMask && packer.clamp(maxLayerType) != maxLayerType) - logWarn(QString("Metatile Layer Type mask '%1' is insufficient to contain all %2 available options.").arg(layerTypeMask).arg(maxLayerType + 1)); + if (layerTypeMask && packer.clamp(maxLayerType) != maxLayerType) { + logWarn(QString("Metatile Layer Type mask '0x%1' is insufficient to contain all %2 available options.") + .arg(QString::number(layerTypeMask, 16).toUpper()) + .arg(maxLayerType + 1)); + } attributePackers.insert(Metatile::Attr::LayerType, packer); } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 1fec8054..9cfd993e 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -373,7 +373,7 @@ void MainWindow::setWildEncountersUIEnabled(bool enabled) { } // Update the UI using information we've read from the user's project files. -bool MainWindow::setProjectSpecificUI() +void MainWindow::setProjectSpecificUI() { this->setWildEncountersUIEnabled(userConfig.getEncounterJsonActive()); @@ -397,7 +397,6 @@ bool MainWindow::setProjectSpecificUI() editor->setCollisionGraphics(); ui->spinBox_SelectedElevation->setMaximum(Block::getMaxElevation()); ui->spinBox_SelectedCollision->setMaximum(Block::getMaxCollision()); - return true; } void MainWindow::mapSortOrder_changed(QAction *action) @@ -543,7 +542,6 @@ bool MainWindow::openProject(QString dir) { } this->projectOpenFailure = !(loadDataStructures() - && setProjectSpecificUI() && populateMapList() && setInitialMap()); @@ -948,8 +946,8 @@ bool MainWindow::loadDataStructures() { && project->readEventGraphics() && project->readSongNames(); - Block::setLayout(); - Metatile::setLayout(project); + project->applyParsedLimits(); + setProjectSpecificUI(); Scripting::populateGlobalObject(this); return success && loadProjectCombos(); diff --git a/src/project.cpp b/src/project.cpp index d2992f61..3e487efa 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -27,7 +27,7 @@ using OrderedJsonDoc = poryjson::JsonDoc; int Project::num_tiles_primary = 512; int Project::num_tiles_total = 1024; -int Project::num_metatiles_primary = 512; // TODO: Verify fits within max +int Project::num_metatiles_primary = 512; int Project::num_pals_primary = 6; int Project::num_pals_total = 13; int Project::max_map_data_size = 10240; // 0x2800 @@ -1865,7 +1865,7 @@ bool Project::readTilesetLabels() { } bool Project::readTilesetProperties() { - static const QStringList definePrefixes{ "\\bNUM_" }; + QStringList definePrefixes{ "\\bNUM_" }; QString filename = projectConfig.getFilePath(ProjectFilePath::constants_fieldmap); fileWatcher.addPath(root + "/" + filename); QMap defines = parser.readCDefines(filename, definePrefixes); @@ -1916,7 +1916,7 @@ bool Project::readTilesetProperties() { // Read data masks for Blocks and metatile attributes. bool Project::readFieldmapMasks() { // We're looking for the suffix "_MASK". Technically our "prefix" is the whole define. - static const QStringList definePrefixes{ "\\b\\w+_MASK" }; + QStringList definePrefixes{ "\\b\\w+_MASK" }; QString globalFieldmap = projectConfig.getFilePath(ProjectFilePath::global_fieldmap); fileWatcher.addPath(root + "/" + globalFieldmap); QMap defines = parser.readCDefines(globalFieldmap, definePrefixes); @@ -1990,7 +1990,7 @@ bool Project::readFieldmapMasks() { } bool Project::readMaxMapDataSize() { - static const QStringList definePrefixes{ "\\bMAX_" }; + QStringList definePrefixes{ "\\bMAX_" }; QString filename = projectConfig.getFilePath(ProjectFilePath::constants_fieldmap); // already in fileWatcher from readTilesetProperties QMap defines = parser.readCDefines(filename, definePrefixes); @@ -2333,7 +2333,7 @@ bool Project::readMiscellaneousConstants() { QString filename = projectConfig.getFilePath(ProjectFilePath::constants_global); fileWatcher.addPath(root + "/" + filename); - static const QStringList definePrefixes("\\bOBJECT_"); + QStringList definePrefixes("\\bOBJECT_"); QMap defines = parser.readCDefines(filename, definePrefixes); auto it = defines.find("OBJECT_EVENT_TEMPLATES_COUNT"); @@ -2705,3 +2705,32 @@ void Project::setImportExportPath(QString filename) { this->importExportPath = QFileInfo(filename).absolutePath(); } + +// The values of some config fields can limit the values of other config fields +// (for example, metatile attributes size limits the metatile attribute masks). +// Others depend on information in the project (for example the default metatile ID +// can be limited by fieldmap defines) +// Once we've read data from the project files we can adjust these accordingly. +void Project::applyParsedLimits() { + // Avoid repeatedly writing the config file + projectConfig.setSaveDisabled(true); + + uint32_t maxMask = Metatile::getMaxAttributesMask(); + projectConfig.setMetatileBehaviorMask(projectConfig.getMetatileBehaviorMask() & maxMask); + projectConfig.setMetatileTerrainTypeMask(projectConfig.getMetatileTerrainTypeMask() & maxMask); + projectConfig.setMetatileEncounterTypeMask(projectConfig.getMetatileEncounterTypeMask() & maxMask); + projectConfig.setMetatileLayerTypeMask(projectConfig.getMetatileLayerTypeMask() & maxMask); + + Block::setLayout(); + Metatile::setLayout(this); + + Project::num_metatiles_primary = qMin(Project::num_metatiles_primary, Block::getMaxMetatileId() + 1); + projectConfig.setDefaultMetatileId(qMin(projectConfig.getDefaultMetatileId(), Block::getMaxMetatileId())); + projectConfig.setDefaultElevation(qMin(projectConfig.getDefaultElevation(), Block::getMaxElevation())); + projectConfig.setDefaultCollision(qMin(projectConfig.getDefaultCollision(), Block::getMaxCollision())); + projectConfig.setCollisionSheetHeight(qMin(projectConfig.getCollisionSheetHeight(), Block::getMaxElevation() + 1)); + projectConfig.setCollisionSheetWidth(qMin(projectConfig.getCollisionSheetWidth(), Block::getMaxCollision() + 1)); + + projectConfig.setSaveDisabled(false); + projectConfig.save(); +} diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index e2387fe8..54452072 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -1,5 +1,4 @@ #include "projectsettingseditor.h" -#include "ui_projectsettingseditor.h" #include "config.h" #include "noscrollcombobox.h" #include "prefab.h" @@ -55,6 +54,15 @@ void ProjectSettingsEditor::connectSignals() { connect(ui->button_HealspotsIcon, &QAbstractButton::clicked, [this](bool) { this->chooseImageFile(ui->lineEdit_HealspotsIcon); }); connect(ui->button_PokemonIcon, &QAbstractButton::clicked, [this](bool) { this->chooseImageFile(ui->lineEdit_PokemonIcon); }); + // Display a warning if a mask value overlaps with another mask in its group. + connect(ui->spinBox_MetatileIdMask, &UIntSpinBox::textChanged, this, &ProjectSettingsEditor::updateBlockMaskOverlapWarning); + connect(ui->spinBox_CollisionMask, &UIntSpinBox::textChanged, this, &ProjectSettingsEditor::updateBlockMaskOverlapWarning); + connect(ui->spinBox_ElevationMask, &UIntSpinBox::textChanged, this, &ProjectSettingsEditor::updateBlockMaskOverlapWarning); + connect(ui->spinBox_BehaviorMask, &UIntSpinBox::textChanged, this, &ProjectSettingsEditor::updateAttributeMaskOverlapWarning); + connect(ui->spinBox_LayerTypeMask, &UIntSpinBox::textChanged, this, &ProjectSettingsEditor::updateAttributeMaskOverlapWarning); + connect(ui->spinBox_EncounterTypeMask, &UIntSpinBox::textChanged, this, &ProjectSettingsEditor::updateAttributeMaskOverlapWarning); + connect(ui->spinBox_TerrainTypeMask, &UIntSpinBox::textChanged, this, &ProjectSettingsEditor::updateAttributeMaskOverlapWarning); + // Record that there are unsaved changes if any of the settings are modified for (auto combo : ui->centralwidget->findChildren()){ if (combo != ui->comboBox_IconSpecies) // Changes to the icon species combo box are just for info display, don't mark as unsaved @@ -112,7 +120,6 @@ void ProjectSettingsEditor::initUi() { ui->spinBox_Collision->setMaximum(Block::getMaxCollision()); ui->spinBox_MaxElevation->setMaximum(Block::getMaxElevation()); ui->spinBox_MaxCollision->setMaximum(Block::getMaxCollision()); - //ui->spinBox_MetatileIdMask->setMinimum(0x1); ui->spinBox_MetatileIdMask->setMaximum(Block::maxValue); ui->spinBox_CollisionMask->setMaximum(Block::maxValue); ui->spinBox_ElevationMask->setMaximum(Block::maxValue); @@ -189,6 +196,47 @@ QList ProjectSettingsEditor::getBorderMetatileIds(bool customSize) { return metatileIds; } +// Show/hide warning for overlapping mask values. These are technically ok, but probably not intended. +// Additionally, Porymap will not properly reflect that the values are linked. +void ProjectSettingsEditor::updateMaskOverlapWarning(QLabel * warning, QList masks) { + // Find any overlapping masks + QMap overlapping; + for (int i = 0; i < masks.length(); i++) + for (int j = i + 1; j < masks.length(); j++) { + if (masks.at(i)->value() & masks.at(j)->value()) + overlapping[i] = overlapping[j] = true; + } + + // It'de nice if we could style this as a persistent red border around the line edit for any + // overlapping masks. As it is editing the border undesirably modifies the arrow buttons. + // This stylesheet will just highlight the currently selected line edit, which is fine enough. + static const QString styleSheet = "QAbstractSpinBox { selection-background-color: rgba(255, 0, 0, 25%) }"; + + // Update warning display + if (warning) warning->setHidden(overlapping.isEmpty()); + for (int i = 0; i < masks.length(); i++) + masks.at(i)->setStyleSheet(overlapping.contains(i) ? styleSheet : ""); +} + +void ProjectSettingsEditor::updateBlockMaskOverlapWarning() { + const auto masks = QList{ + ui->spinBox_MetatileIdMask, + ui->spinBox_CollisionMask, + ui->spinBox_ElevationMask, + }; + this->updateMaskOverlapWarning(ui->label_OverlapWarningBlocks, masks); +} + +void ProjectSettingsEditor::updateAttributeMaskOverlapWarning() { + const auto masks = QList{ + ui->spinBox_BehaviorMask, + ui->spinBox_LayerTypeMask, + ui->spinBox_EncounterTypeMask, + ui->spinBox_TerrainTypeMask, + }; + this->updateMaskOverlapWarning(ui->label_OverlapWarningMetatiles, masks); +} + void ProjectSettingsEditor::updateAttributeLimits(const QString &attrSize) { static const QMap limits { {"1", 0xFF}, diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index 34e24b3f..7d680b54 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -113,7 +113,7 @@ void TilesetEditor::initUi() { void TilesetEditor::setAttributesUi() { // Behavior - if (projectConfig.getMetatileBehaviorMask() != 0) { + if (projectConfig.getMetatileBehaviorMask()) { for (int num : project->metatileBehaviorMapInverse.keys()) { this->ui->comboBox_metatileBehaviors->addItem(project->metatileBehaviorMapInverse[num], num); } diff --git a/src/ui/uintspinbox.cpp b/src/ui/uintspinbox.cpp index 6579ed6b..789a1662 100644 --- a/src/ui/uintspinbox.cpp +++ b/src/ui/uintspinbox.cpp @@ -104,29 +104,37 @@ void UIntSpinBox::onEditFinished() { QString input = this->lineEdit()->text(); auto state = this->validate(input, pos); + if (state == QValidator::Invalid) + return; + + auto newValue = m_value; if (state == QValidator::Acceptable) { // Valid input - m_value = this->valueFromText(input); + newValue = this->valueFromText(input); } else if (state == QValidator::Intermediate) { // User has deleted all the number text. // If they did this by selecting all text and then hitting delete // make sure to put the cursor back in front of the prefix. - m_value = m_minimum; + newValue = m_minimum; this->lineEdit()->setCursorPosition(m_prefix.length()); } + if (newValue != m_value) { + m_value = newValue; + emit this->valueChanged(m_value); + } + emit this->textChanged(input); } -void UIntSpinBox::stepBy(int steps) -{ - auto new_value = m_value; - if (steps < 0 && new_value + steps > new_value) { - new_value = 0; - } else if (steps > 0 && new_value + steps < new_value) { - new_value = UINT_MAX; +void UIntSpinBox::stepBy(int steps) { + auto newValue = m_value; + if (steps < 0 && newValue + steps > newValue) { + newValue = 0; + } else if (steps > 0 && newValue + steps < newValue) { + newValue = UINT_MAX; } else { - new_value += steps; + newValue += steps; } - this->setValue(new_value); + this->setValue(newValue); } QString UIntSpinBox::stripped(QString input) const { @@ -144,8 +152,9 @@ QValidator::State UIntSpinBox::validate(QString &input, int &pos) const { if (copy.isEmpty()) return QValidator::Intermediate; - // Editing the prefix (if not deleting all text) is not allowed - if (pos < m_prefix.length()) + // Editing the prefix (if not deleting all text) is not allowed. + // Nor is editing beyond the maximum value's character limit. + if (pos < m_prefix.length() || pos > (m_numChars + m_prefix.length())) return QValidator::Invalid; bool ok;