From 109c8a3edc9ff83b7265347e0848e7fd2d34e0bd Mon Sep 17 00:00:00 2001 From: tetektoza Date: Sat, 3 Feb 2024 16:54:07 +0100 Subject: [PATCH] Views: Cleanup LevelCelView a bit --- source/undostack/framecmds.cpp | 5 +- source/undostack/framecmds.h | 5 +- source/undostack/undomacro.cpp | 12 ++ source/undostack/undomacro.h | 4 + source/views/celview.cpp | 2 +- source/views/levelcelview.cpp | 322 ++++++++++++++++++--------------- source/views/levelcelview.h | 18 +- 7 files changed, 212 insertions(+), 156 deletions(-) diff --git a/source/undostack/framecmds.cpp b/source/undostack/framecmds.cpp index 5045c036..e66ca540 100644 --- a/source/undostack/framecmds.cpp +++ b/source/undostack/framecmds.cpp @@ -40,10 +40,9 @@ void ReplaceFrameCommand::redo() emit this->replaced(frameIndexToReplace, imgToReplace); } -AddFrameCommand::AddFrameCommand(int index, QImage &img, IMAGE_FILE_MODE mode) +AddFrameCommand::AddFrameCommand(int index, QImage &img) : m_index(index) , m_image(std::move(img)) - , m_mode(mode) { } @@ -54,5 +53,5 @@ void AddFrameCommand::undo() void AddFrameCommand::redo() { - emit this->added(m_index, m_image, m_mode); + emit this->added(m_index, m_image); } diff --git a/source/undostack/framecmds.h b/source/undostack/framecmds.h index 79035025..58e0c85d 100644 --- a/source/undostack/framecmds.h +++ b/source/undostack/framecmds.h @@ -48,7 +48,7 @@ class AddFrameCommand : public QObject, public Command { Q_OBJECT public: - explicit AddFrameCommand(int index, QImage &image, IMAGE_FILE_MODE mode); + explicit AddFrameCommand(int index, QImage &image); ~AddFrameCommand() = default; void undo() override; @@ -56,10 +56,9 @@ class AddFrameCommand : public QObject, public Command { signals: void undoAdded(int index); - void added(int index, const QImage &image, IMAGE_FILE_MODE mode); + void added(int index, const QImage &image); private: QImage m_image; int m_index = 0; - IMAGE_FILE_MODE m_mode; }; diff --git a/source/undostack/undomacro.cpp b/source/undostack/undomacro.cpp index f7c2f5bc..71c4620e 100644 --- a/source/undostack/undomacro.cpp +++ b/source/undostack/undomacro.cpp @@ -9,11 +9,23 @@ UserData::UserData(QString labelText, QString cancelButtonText, std::pair(userData.labelText(), userData.cancelButtonText(), std::make_pair(userData.min(), userData.max()))) { } +void UndoMacroFactory::setUserData(const UserData &&userData) +{ + m_userData = std::make_unique(userData.labelText(), userData.cancelButtonText(), std::make_pair(userData.min(), userData.max())); +} + void UndoMacroFactory::add(std::unique_ptr cmd) { m_commands.push_back(std::move(cmd)); diff --git a/source/undostack/undomacro.h b/source/undostack/undomacro.h index 98696319..60687422 100644 --- a/source/undostack/undomacro.h +++ b/source/undostack/undomacro.h @@ -14,6 +14,7 @@ class UserData { public: UserData(QString labelText, QString cancelButtonText, std::pair &&minMax); + UserData(QString labelText, QString cancelButtonText); ~UserData() = default; [[nodiscard]] int min() const @@ -49,8 +50,11 @@ class UndoMacroFactory { public: UndoMacroFactory(UserData &&userData); + UndoMacroFactory() = default; ~UndoMacroFactory() = default; + void setUserData(const UserData &&userData); + void add(std::unique_ptr cmd); [[nodiscard]] std::vector> &cmds() { diff --git a/source/views/celview.cpp b/source/views/celview.cpp index 3a0a6002..92e4eba8 100644 --- a/source/views/celview.cpp +++ b/source/views/celview.cpp @@ -221,7 +221,7 @@ void CelView::sendAddFrameCmd(IMAGE_FILE_MODE mode, int index, const QString &im }; auto connectCommand = [&](QImage &img) -> std::unique_ptr { - auto command = std::make_unique(index, img, mode); + auto command = std::make_unique(index, img); // Connect signals which will be called upon redo/undo operations of the undostack QObject::connect(command.get(), &AddFrameCommand::added, this, &CelView::insertFrames); diff --git a/source/views/levelcelview.cpp b/source/views/levelcelview.cpp index 1e0bc627..b3cdba61 100644 --- a/source/views/levelcelview.cpp +++ b/source/views/levelcelview.cpp @@ -18,6 +18,95 @@ #include #include +namespace { + +int getClickedSubtile(unsigned x, unsigned y, unsigned width, unsigned height) +{ + // | | + // | | + // 2| 0 |1 + // | | + // | | + + // The perspective lets us know the floor heigth based on the width + int wallHeight = height - (width / 4 + width / 8); + + if (y < wallHeight) { + if (x < width / 4) { + return 2; + } + if (x > width - width / 4) { + return 1; + } + return 0; + } + + // \0/ + // 2 X 1 + // / \ + // / 3 \ + // / \ + + y -= height - width / 2; + + int y1 = width / 2 * x / width; // y of 1st diagonal at x + int y2 = width / 2 - y1; // y of 2nd diagonal at x + + if (y < y1) { + if (y < y2) + return 0; // top + else + return 1; // right + } else { + if (y < y2) + return 2; // left + else + return 3; // bottom + } +} + +/** + * @brief Extracts subimages from given image + * + * This function extracts 32x32 subimages from an image that is + * 32x32 divisible and returns them as an input parameter to the std::vector. + * This is because LevelCelView supports only 32x32 frames. + * + * @return Returns QList of frame indices to be replaced if needed + */ +QList extractSubImages(const QImage &image, std::vector &images, int frameIndex) +{ + QList frameIndicesList; + + // TODO: merge with LevelCelView::insertSubtile ? + QImage subImage = QImage(MICRO_WIDTH, MICRO_HEIGHT, QImage::Format_ARGB32); + for (int y = 0; y < image.height(); y += MICRO_HEIGHT) { + for (int x = 0; x < image.width(); x += MICRO_WIDTH) { + + bool hasColor = false; + for (int j = 0; j < MICRO_HEIGHT; j++) { + for (int i = 0; i < MICRO_WIDTH; i++) { + const QColor color = image.pixelColor(x + i, y + j); + if (color.alpha() >= COLOR_ALPHA_LIMIT) { + hasColor = true; + } + subImage.setPixelColor(i, j, color); + } + } + frameIndicesList.append(hasColor ? frameIndex + 1 : 0); + if (!hasColor) { + continue; + } + + images.push_back(subImage); + } + } + + return frameIndicesList; +} + +} // namespace + LevelCelView::LevelCelView(std::shared_ptr us, QWidget *parent) : QWidget(parent) , undoStack(std::move(us)) @@ -95,69 +184,6 @@ void LevelCelView::update() this->tabFrameWidget->initialize(this, this->gfx); } -int LevelCelView::getCurrentFrameIndex() -{ - return this->currentFrameIndex; -} - -int LevelCelView::getCurrentSubtileIndex() -{ - return this->currentSubtileIndex; -} - -int LevelCelView::getCurrentTileIndex() -{ - return this->currentTileIndex; -} - -namespace { - -int getClickedSubtile(unsigned x, unsigned y, unsigned width, unsigned height) -{ - // | | - // | | - // 2| 0 |1 - // | | - // | | - - // The perspective lets us know the floor heigth based on the width - int wallHeight = height - (width / 4 + width / 8); - - if (y < wallHeight) { - if (x < width / 4) { - return 2; - } - if (x > width - width / 4) { - return 1; - } - return 0; - } - - // \0/ - // 2 X 1 - // / \ - // / 3 \ - // / \ - - y -= height - width / 2; - - int y1 = width / 2 * x / width; // y of 1st diagonal at x - int y2 = width / 2 - y1; // y of 2nd diagonal at x - - if (y < y1) { - if (y < y2) - return 0; // top - else - return 1; // right - } else { - if (y < y2) - return 2; // left - else - return 3; // bottom - } -} -} // namespace - void LevelCelView::framePixelClicked(unsigned x, unsigned y) { quint8 index = 0; @@ -241,77 +267,12 @@ void LevelCelView::insertImageFiles(IMAGE_FILE_MODE mode, const QStringList &ima } } -void LevelCelView::assignFrames(const QImage &image, int subtileIndex, int frameIndex) -{ - QList frameIndicesList; - - // TODO: merge with LevelCelView::insertSubtile ? - QImage subImage = QImage(MICRO_WIDTH, MICRO_HEIGHT, QImage::Format_ARGB32); - for (int y = 0; y < image.height(); y += MICRO_HEIGHT) { - for (int x = 0; x < image.width(); x += MICRO_WIDTH) { - // subImage.fill(Qt::transparent); - - bool hasColor = false; - for (int j = 0; j < MICRO_HEIGHT; j++) { - for (int i = 0; i < MICRO_WIDTH; i++) { - const QColor color = image.pixelColor(x + i, y + j); - if (color.alpha() >= COLOR_ALPHA_LIMIT) { - hasColor = true; - } - subImage.setPixelColor(i, j, color); - } - } - frameIndicesList.append(hasColor ? frameIndex + 1 : 0); - if (!hasColor) { - continue; - } - - D1GfxFrame *frame = this->gfx->insertFrame(frameIndex, subImage); - LevelTabFrameWidget::selectFrameType(frame); - frameIndex++; - } - } - - if (subtileIndex >= 0) { - this->min->getCelFrameIndices(subtileIndex).swap(frameIndicesList); - // reset subtile flags - this->sol->setSubtileProperties(subtileIndex, 0); - } -} - -void LevelCelView::insertFrame(IMAGE_FILE_MODE mode, int index, const QImage &image) -{ - // FIXME: investigate if adding multiple frames, and having a frame that is not in - // proper dimensions will screw up frame list, especially in appending operations - if ((image.width() % MICRO_WIDTH) != 0 || (image.height() % MICRO_HEIGHT) != 0) { - QMessageBox::critical(this, tr("Error!"), tr("Wrong frame dimensions!\n" - "Image should have dimensions %1x%2px (w x h).\n" - "Image that you wanted to insert has %3x%4px dimensions.") - .arg(MICRO_WIDTH) - .arg(MICRO_HEIGHT) - .arg(image.width()) - .arg(image.height())); - return; - } - - if (mode == IMAGE_FILE_MODE::AUTO) { - // check for subtile dimensions to be more lenient than EXPORT_LVLFRAMES_PER_LINE - unsigned subtileWidth = this->min->getSubtileWidth() * MICRO_WIDTH; - unsigned subtileHeight = this->min->getSubtileHeight() * MICRO_HEIGHT; - - if ((image.width() % subtileWidth) == 0 && (image.height() % subtileHeight) == 0) { - return; // this is a subtile or a tile (or subtiles or tiles) -> ignore - } - } - - this->assignFrames(image, -1, index); -} - -void LevelCelView::insertFrames(int index, const QImage &image, IMAGE_FILE_MODE mode) +void LevelCelView::insertFrames(int index, const QImage &image) { int prevFrameCount = this->gfx->getFrameCount(); - this->insertFrame(mode, index, image); + D1GfxFrame *frame = this->gfx->insertFrame(index, image); + LevelTabFrameWidget::selectFrameType(frame); int deltaFrameCount = this->gfx->getFrameCount() - prevFrameCount; if (deltaFrameCount == 0) { @@ -380,16 +341,53 @@ void LevelCelView::sendAddFrameCmd(IMAGE_FILE_MODE mode, int index, const QStrin return false; } + // FIXME: investigate if adding multiple frames, and having a frame that is not in + // proper dimensions will screw up frame list, especially in appending operations + if ((img.width() % MICRO_WIDTH) != 0 || (img.height() % MICRO_HEIGHT) != 0) { + QMessageBox::critical(this, tr("Error!"), tr("Wrong frame dimensions!\n" + "Image should have dimensions %1x%2px (w x h).\n" + "Image that you wanted to insert has %3x%4px dimensions.") + .arg(MICRO_WIDTH) + .arg(MICRO_HEIGHT) + .arg(img.width()) + .arg(img.height())); + return false; + } + + if (mode == IMAGE_FILE_MODE::AUTO) { + // check for subtile dimensions to be more lenient than EXPORT_LVLFRAMES_PER_LINE + unsigned subtileWidth = this->min->getSubtileWidth() * MICRO_WIDTH; + unsigned subtileHeight = this->min->getSubtileHeight() * MICRO_HEIGHT; + + if ((img.width() % subtileWidth) == 0 && (img.height() % subtileHeight) == 0) { + return false; // this is a subtile or a tile (or subtiles or tiles) -> ignore + } + } + return true; }; - auto connectCommand = [&](QImage &img) -> std::unique_ptr { - auto command = std::make_unique(index, img, mode); - + auto connectCommand = [&](std::unique_ptr &command) { // Connect signals which will be called upon redo/undo operations of the undostack - QObject::connect(command.get(), &AddFrameCommand::added, this, static_cast(&LevelCelView::insertFrames)); + QObject::connect(command.get(), &AddFrameCommand::added, this, static_cast(&LevelCelView::insertFrames)); QObject::connect(command.get(), &AddFrameCommand::undoAdded, this, &LevelCelView::removeCurrentFrame); - return command; + }; + + auto addSubImagesToMacro = [&](QImage &image, UndoMacroFactory ¯oFactory) -> int { + // Here we are extracting sub-images from the image which are 32x32 divisible + std::vector subImages; + subImages.reserve(image.width() / MICRO_WIDTH * image.height() / MICRO_HEIGHT); + extractSubImages(image, subImages, index); + + for (auto &subImage : subImages) { + auto command = std::make_unique(index, subImage); + connectCommand(command); + macroFactory.add(std::move(command)); + index++; + } + + // Return number of subimages extracted + return static_cast(subImages.size()); }; // If we have more than one image, then we want to use a macro @@ -397,7 +395,9 @@ void LevelCelView::sendAddFrameCmd(IMAGE_FILE_MODE mode, int index, const QStrin QObject::connect(this->undoStack.get(), &UndoStack::initializeWidget, dynamic_cast(this->window()), &MainWindow::setupUndoMacroWidget, Qt::UniqueConnection); QObject::connect(this->undoStack.get(), &UndoStack::updateWidget, dynamic_cast(this->window()), &MainWindow::updateUndoMacroWidget, Qt::UniqueConnection); - UndoMacroFactory macroFactory({ "Inserting frames...", "Abort", { 0, reader.imageCount() } }); + int totalNumSubImages = 0; + UserData userData("Inserting frames...", "Abort"); + UndoMacroFactory macroFactory; int numImages = 0; while (numImages != reader.imageCount()) { @@ -405,13 +405,16 @@ void LevelCelView::sendAddFrameCmd(IMAGE_FILE_MODE mode, int index, const QStrin if (!readImage(image)) return; - auto command = connectCommand(image); - - macroFactory.add(std::move(command)); + int numSubImages = addSubImagesToMacro(image, macroFactory); + totalNumSubImages += numSubImages; numImages++; } + // We have to set maximum after, because we don't know how many sub images there will be + userData.setMax(totalNumSubImages); + macroFactory.setUserData(std::move(userData)); + undoStack->addMacro(macroFactory); return; } @@ -420,16 +423,32 @@ void LevelCelView::sendAddFrameCmd(IMAGE_FILE_MODE mode, int index, const QStrin if (!readImage(image)) return; - auto command = connectCommand(image); + // If the image is not exactly 32x32, then we have subimages. So extract them and pass them as a macro + // Otherwise insert them as a normal UndoStack command. + if (image.width() != MICRO_WIDTH && image.height() != MICRO_HEIGHT) { + QObject::connect(this->undoStack.get(), &UndoStack::initializeWidget, dynamic_cast(this->window()), &MainWindow::setupUndoMacroWidget, Qt::UniqueConnection); + QObject::connect(this->undoStack.get(), &UndoStack::updateWidget, dynamic_cast(this->window()), &MainWindow::updateUndoMacroWidget, Qt::UniqueConnection); + + int numOfSubImages = image.width() / MICRO_WIDTH * image.height() / MICRO_HEIGHT; + UndoMacroFactory macroFactory({ "Inserting frames...", "Abort", { 0, numOfSubImages } }); - undoStack->push(std::move(command)); + addSubImagesToMacro(image, macroFactory); + + undoStack->addMacro(macroFactory); + } else { + auto command = std::make_unique(index, image); + connectCommand(command); + + undoStack->push(std::move(command)); + } } void LevelCelView::insertFrame(int index, const QImage image) { int prevFrameCount = this->gfx->getFrameCount(); - this->insertFrame(IMAGE_FILE_MODE::FRAME, index, image); + D1GfxFrame *frame = this->gfx->insertFrame(index, image); + LevelTabFrameWidget::selectFrameType(frame); int deltaFrameCount = this->gfx->getFrameCount() - prevFrameCount; if (deltaFrameCount == 0) { @@ -892,7 +911,22 @@ void LevelCelView::replaceCurrentSubtile(const QString &imagefilePath) } int subtileIndex = this->currentSubtileIndex; - this->assignFrames(image, subtileIndex, this->gfx->getFrameCount()); + int frameIndex = this->gfx->getFrameCount(); + + // Here we are extracting sub-images from the image which are 32x32 divisible + std::vector subImages; + subImages.reserve(image.width() / MICRO_WIDTH * image.height() / MICRO_HEIGHT); + QList frameIndicesList = extractSubImages(image, subImages, frameIndex); + + for (auto &subImage : subImages) { + D1GfxFrame *frame = this->gfx->insertFrame(frameIndex, subImage); + LevelTabFrameWidget::selectFrameType(frame); + frameIndex++; + } + + this->min->getCelFrameIndices(subtileIndex).swap(frameIndicesList); + // reset subtile flags + this->sol->setSubtileProperties(subtileIndex, 0); // update the view this->update(); diff --git a/source/views/levelcelview.h b/source/views/levelcelview.h index 055e009c..6f98e691 100644 --- a/source/views/levelcelview.h +++ b/source/views/levelcelview.h @@ -48,9 +48,18 @@ class LevelCelView : public QWidget { void initialize(D1Gfx *gfx, D1Min *min, D1Til *til, D1Sol *sol, D1Amp *amp); - int getCurrentFrameIndex(); - int getCurrentSubtileIndex(); - int getCurrentTileIndex(); + [[nodiscard]] int getCurrentFrameIndex() const + { + return this->currentFrameIndex; + } + [[nodiscard]] int getCurrentSubtileIndex() const + { + return this->currentSubtileIndex; + } + [[nodiscard]] int getCurrentTileIndex() const + { + return this->currentTileIndex; + } void framePixelClicked(unsigned x, unsigned y); @@ -89,9 +98,8 @@ class LevelCelView : public QWidget { void update(); void collectFrameUsers(int frameIndex, QList &users) const; void collectSubtileUsers(int subtileIndex, QList &users) const; - void insertFrame(IMAGE_FILE_MODE mode, int index, const QImage &image); void insertFrames(IMAGE_FILE_MODE mode, const QStringList &imagefilePaths, bool append); - void insertFrames(int index, const QImage &image, IMAGE_FILE_MODE mode); + void insertFrames(int index, const QImage &image); void insertSubtile(int subtileIndex, const QImage &image); void insertSubtiles(IMAGE_FILE_MODE mode, int index, const QImage &image); void insertSubtiles(IMAGE_FILE_MODE mode, int index, const QString &imagefilePath);