diff --git a/include/core/maplayout.h b/include/core/maplayout.h index 7ec240b6..cdd3b5d6 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -69,6 +69,17 @@ public: QUndoStack editHistory; + // to simplify new layout settings transfer between functions + struct SimpleSettings { + QString id; + QString name; + int width; + int height; + QString tileset_primary_label; + QString tileset_secondary_label; + QString from_id = QString(); + }; + public: Layout *copy(); void copyFrom(Layout *other); diff --git a/include/project.h b/include/project.h index 85c8e3de..49bcd6aa 100644 --- a/include/project.h +++ b/include/project.h @@ -139,6 +139,7 @@ public: bool loadMapData(Map*); bool readMapLayouts(); Layout *loadLayout(QString layoutId); + Layout *createNewLayout(Layout::SimpleSettings &layoutSettings); bool loadLayout(Layout *); bool loadMapLayout(Map*); bool loadLayoutTilesets(Layout *); @@ -235,8 +236,8 @@ public: private: void updateLayout(Layout *); - void setNewMapBlockdata(Map* map); - void setNewMapBorder(Map *map); + void setNewLayoutBlockdata(Layout *layout); + void setNewLayoutBorder(Layout *layout); void setNewMapEvents(Map *map); void setNewMapConnections(Map *map); diff --git a/include/ui/maplistmodels.h b/include/ui/maplistmodels.h index a2babda6..bb2fd07a 100644 --- a/include/ui/maplistmodels.h +++ b/include/ui/maplistmodels.h @@ -155,6 +155,7 @@ public: QStandardItem *createLayoutItem(QString layoutId); QStandardItem *createMapItem(QString mapName); + QStandardItem *insertLayoutItem(QString layoutId); QStandardItem *insertMapItem(QString mapName, QString layoutId); QStandardItem *getItem(const QModelIndex &index) const; diff --git a/src/editor.cpp b/src/editor.cpp index 5ed97782..aaad47db 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -74,6 +74,7 @@ void Editor::save() { } else if (this->project && this->layout) { this->project->saveLayout(this->layout); + this->project->saveAllDataStructures(); } } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 4d31fb6c..8a9f95b5 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1309,7 +1309,121 @@ void MainWindow::mapListAddGroup() { } void MainWindow::mapListAddLayout() { - // this->layoutTreeModel->insertMapItem(newMapName, newMap->layout->id); + if (!editor || !editor->project) return; + + QDialog dialog(this, Qt::WindowTitleHint | Qt::WindowCloseButtonHint); + dialog.setWindowModality(Qt::ApplicationModal); + QDialogButtonBox newItemButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &dialog); + connect(&newItemButtonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); + + QLineEdit *newNameEdit = new QLineEdit(&dialog); + newNameEdit->setClearButtonEnabled(true); + + static const QRegularExpression re_validChars("[_A-Za-z0-9]*$"); + QRegularExpressionValidator *validator = new QRegularExpressionValidator(re_validChars); + newNameEdit->setValidator(validator); + + QLabel *newId = new QLabel("LAYOUT_", &dialog); + connect(newNameEdit, &QLineEdit::textChanged, [&](QString text){ + newId->setText(Layout::layoutConstantFromName(text.remove("_Layout"))); + }); + + NoScrollComboBox *useExistingCombo = new NoScrollComboBox(&dialog); + useExistingCombo->addItems(this->editor->project->mapLayoutsTable); + useExistingCombo->setEnabled(false); + + QCheckBox *useExistingCheck = new QCheckBox(&dialog); + + QLabel *errorMessageLabel = new QLabel(&dialog); + errorMessageLabel->setVisible(false); + errorMessageLabel->setStyleSheet("QLabel { background-color: rgba(255, 0, 0, 25%) }"); + QString errorMessage; + + QComboBox *primaryCombo = new QComboBox(&dialog); + primaryCombo->addItems(this->editor->project->primaryTilesetLabels); + QComboBox *secondaryCombo = new QComboBox(&dialog); + secondaryCombo->addItems(this->editor->project->secondaryTilesetLabels); + + QSpinBox *widthSpin = new QSpinBox(&dialog); + QSpinBox *heightSpin = new QSpinBox(&dialog); + + widthSpin->setMinimum(1); + heightSpin->setMinimum(1); + widthSpin->setMaximum(this->editor->project->getMaxMapWidth()); + heightSpin->setMaximum(this->editor->project->getMaxMapHeight()); + + connect(useExistingCheck, &QCheckBox::stateChanged, [&](int state){ + bool useExisting = (state == Qt::Checked); + useExistingCombo->setEnabled(useExisting); + primaryCombo->setEnabled(!useExisting); + secondaryCombo->setEnabled(!useExisting); + widthSpin->setEnabled(!useExisting); + heightSpin->setEnabled(!useExisting); + }); + + QFormLayout form(&dialog); + form.addRow("New Layout Name", newNameEdit); + form.addRow("New Layout ID", newId); + form.addRow("Copy Existing Layout", useExistingCheck); + form.addRow("", useExistingCombo); + form.addRow("Primary Tileset", primaryCombo); + form.addRow("Secondary Tileset", secondaryCombo); + form.addRow("Layout Width", widthSpin); + form.addRow("Layout Height", heightSpin); + form.addRow("", errorMessageLabel); + + connect(&newItemButtonBox, &QDialogButtonBox::accepted, [&](){ + // verify some things + bool issue = false; + QString tryLayoutName = newNameEdit->text(); + // name not empty + if (tryLayoutName.isEmpty()) { + errorMessage = "Name cannot be empty"; + issue = true; + } + // unique layout name & id + else if (this->editor->project->mapLayoutsTable.contains(newId->text()) + || this->editor->project->layoutIdsToNames.find(tryLayoutName) != this->editor->project->layoutIdsToNames.end()) { + errorMessage = "Layout Name / ID is not unique"; + issue = true; + } + // from id is existing value + else if (useExistingCheck->isChecked()) { + if (!this->editor->project->mapLayoutsTable.contains(useExistingCombo->currentText())) { + errorMessage = "Existing layout ID is not valid"; + issue = true; + } + } + + if (issue) { + // show error + errorMessageLabel->setText(errorMessage); + errorMessageLabel->setVisible(true); + } + else { + dialog.accept(); + } + }); + + form.addRow(&newItemButtonBox); + + if (dialog.exec() == QDialog::Accepted) { + Layout::SimpleSettings layoutSettings; + QString layoutName = newNameEdit->text(); + layoutSettings.name = layoutName; + layoutSettings.id = Layout::layoutConstantFromName(layoutName.remove("_Layout")); + if (useExistingCheck->isChecked()) { + layoutSettings.from_id = useExistingCombo->currentText(); + } else { + layoutSettings.width = widthSpin->value(); + layoutSettings.height = heightSpin->value(); + layoutSettings.tileset_primary_label = primaryCombo->currentText(); + layoutSettings.tileset_secondary_label = secondaryCombo->currentText(); + } + Layout *newLayout = this->editor->project->createNewLayout(layoutSettings); + QStandardItem *item = this->layoutTreeModel->insertLayoutItem(newLayout->id); + setLayout(newLayout->id); + } } void MainWindow::mapListAddArea() { @@ -1322,10 +1436,10 @@ void MainWindow::mapListAddItem() { this->mapListAddGroup(); break; case 1: - this->mapListAddLayout(); + this->mapListAddArea(); break; case 2: - this->mapListAddArea(); + this->mapListAddLayout(); break; } } diff --git a/src/project.cpp b/src/project.cpp index 41f9ccf9..81f472ed 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -385,6 +385,60 @@ QString Project::readMapLocation(QString map_name) { return ParseUtil::jsonToQString(mapObj["region_map_section"]); } +Layout *Project::createNewLayout(Layout::SimpleSettings &layoutSettings) { + QString basePath = projectConfig.getFilePath(ProjectFilePath::data_layouts_folders); + Layout *layout; + + // Handle the case where we are copying from an existing layout first. + if (!layoutSettings.from_id.isEmpty()) { + // load from layout + loadLayout(mapLayouts[layoutSettings.from_id]); + + layout = mapLayouts[layoutSettings.from_id]->copy(); + layout->name = layoutSettings.name; + layout->id = layoutSettings.id; + layout->border_path = QString("%1%2/border.bin").arg(basePath, layoutSettings.name); + layout->blockdata_path = QString("%1%2/map.bin").arg(basePath, layoutSettings.name); + } + else { + layout = new Layout; + + layout->name = layoutSettings.name; + layout->id = layoutSettings.id; + layout->width = layoutSettings.width; + layout->height = layoutSettings.height; + layout->border_width = DEFAULT_BORDER_WIDTH; + layout->border_height = DEFAULT_BORDER_HEIGHT; + layout->tileset_primary_label = layoutSettings.tileset_primary_label; + layout->tileset_secondary_label = layoutSettings.tileset_secondary_label; + layout->border_path = QString("%1%2/border.bin").arg(basePath, layoutSettings.name); + layout->blockdata_path = QString("%1%2/map.bin").arg(basePath, layoutSettings.name); + + setNewLayoutBlockdata(layout); + setNewLayoutBorder(layout); + } + + // Create a new directory for the layout + QString newLayoutDir = QString(root + "/%1%2").arg(projectConfig.getFilePath(ProjectFilePath::data_layouts_folders), layout->name); + if (!QDir::root().mkdir(newLayoutDir)) { + logError(QString("Error: failed to create directory for new layout: '%1'").arg(newLayoutDir)); + delete layout; + return nullptr; + } + + mapLayouts.insert(layout->id, layout); + mapLayoutsMaster.insert(layout->id, layout->copy()); + mapLayoutsTable.append(layout->id); + mapLayoutsTableMaster.append(layout->id); + layoutIdsToNames.insert(layout->id, layout->name); + + saveLayout(layout); + + this->loadLayout(layout); + + return layout; +} + bool Project::loadLayout(Layout *layout) { // !TODO: make sure this doesn't break anything, maybe do something better. new layouts work too? if (!layout->loaded) { @@ -1119,16 +1173,16 @@ bool Project::loadBlockdata(Layout *layout) { return true; } -void Project::setNewMapBlockdata(Map *map) { - map->layout->blockdata.clear(); - int width = map->getWidth(); - int height = map->getHeight(); +void Project::setNewLayoutBlockdata(Layout *layout) { + layout->blockdata.clear(); + int width = layout->getWidth(); + int height = layout->getHeight(); Block block(projectConfig.getDefaultMetatileId(), projectConfig.getDefaultCollision(), projectConfig.getDefaultElevation()); for (int i = 0; i < width * height; i++) { - map->layout->blockdata.append(block); + layout->blockdata.append(block); } - map->layout->lastCommitBlocks.blocks = map->layout->blockdata; - map->layout->lastCommitBlocks.layoutDimensions = QSize(width, height); + layout->lastCommitBlocks.blocks = layout->blockdata; + layout->lastCommitBlocks.layoutDimensions = QSize(width, height); } bool Project::loadLayoutBorder(Layout *layout) { @@ -1147,27 +1201,27 @@ bool Project::loadLayoutBorder(Layout *layout) { return true; } -void Project::setNewMapBorder(Map *map) { - map->layout->border.clear(); - int width = map->getBorderWidth(); - int height = map->getBorderHeight(); +void Project::setNewLayoutBorder(Layout *layout) { + layout->border.clear(); + int width = layout->getBorderWidth(); + int height = layout->getBorderHeight(); const QList configMetatileIds = projectConfig.getNewMapBorderMetatileIds(); if (configMetatileIds.length() != width * height) { // Border size doesn't match the number of default border metatiles. // Fill the border with empty metatiles. for (int i = 0; i < width * height; i++) { - map->layout->border.append(0); + layout->border.append(0); } } else { // Fill the border with the default metatiles from the config. for (int i = 0; i < width * height; i++) { - map->layout->border.append(configMetatileIds.at(i)); + layout->border.append(configMetatileIds.at(i)); } } - map->layout->lastCommitBlocks.border = map->layout->border; - map->layout->lastCommitBlocks.borderDimensions = QSize(width, height); + layout->lastCommitBlocks.border = layout->border; + layout->lastCommitBlocks.borderDimensions = QSize(width, height); } void Project::saveLayoutBorder(Layout *layout) { @@ -1809,10 +1863,10 @@ Map* Project::addNewMapToGroup(QString mapName, int groupNum, Map *newMap, bool mapLayoutsTable.append(newMap->layoutId); layoutIdsToNames.insert(newMap->layout->id, newMap->layout->name); if (!importedMap) { - setNewMapBlockdata(newMap); + setNewLayoutBlockdata(newMap->layout); } if (newMap->layout->border.isEmpty()) { - setNewMapBorder(newMap); + setNewLayoutBorder(newMap->layout); } } diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index ee60affc..a572c7eb 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -535,6 +535,11 @@ QStandardItem *LayoutTreeModel::createMapItem(QString mapName) { return map; } +QStandardItem *LayoutTreeModel::insertLayoutItem(QString layoutId) { + QStandardItem *layoutItem = this->createLayoutItem(layoutId); + this->root->appendRow(layoutItem); +} + QStandardItem *LayoutTreeModel::insertMapItem(QString mapName, QString layoutId) { QStandardItem *layout = nullptr; if (this->layoutItems.contains(layoutId)) { @@ -591,6 +596,7 @@ QVariant LayoutTreeModel::data(const QModelIndex &index, int role) const { int col = index.column(); if (role == Qt::DecorationRole) { + static QIcon mapGrayIcon = QIcon(QStringLiteral(":/icons/map_grayed.ico")); static QIcon mapIcon = QIcon(QStringLiteral(":/icons/map.ico")); static QIcon mapEditedIcon = QIcon(QStringLiteral(":/icons/map_edited.ico")); static QIcon mapOpenedIcon = QIcon(QStringLiteral(":/icons/map_opened.ico")); @@ -607,6 +613,9 @@ QVariant LayoutTreeModel::data(const QModelIndex &index, int role) const { if (this->project->mapLayouts.value(layoutId)->hasUnsavedChanges()) { return mapEditedIcon; } + else if (!this->project->mapLayouts[layoutId]->loaded) { + return mapGrayIcon; + } } return mapIcon; }