From bf00ff0e41b4938caee148f0895668b5d4622cec Mon Sep 17 00:00:00 2001 From: Gary Talent Date: Wed, 11 Mar 2020 00:17:07 -0500 Subject: [PATCH] [nostalgia/core] Add ability to save tile sheets and store columns and rows in ng file --- sample_project/Palettes/Dirt.npal | 1 - sample_project/TileSheets/Charset.ng | Bin 786 -> 790 bytes sample_project/TileSheets/Dirt.ng | Bin 53 -> 0 bytes src/nostalgia/core/gfx.hpp | 9 +- src/nostalgia/core/studio/TileSheetEditor.qml | 4 + src/nostalgia/core/studio/consts.hpp | 1 + src/nostalgia/core/studio/tilesheeteditor.cpp | 190 ++++++++++++++---- src/nostalgia/core/studio/tilesheeteditor.hpp | 43 ++-- 8 files changed, 197 insertions(+), 51 deletions(-) delete mode 100644 sample_project/Palettes/Dirt.npal delete mode 100644 sample_project/TileSheets/Dirt.ng diff --git a/sample_project/Palettes/Dirt.npal b/sample_project/Palettes/Dirt.npal deleted file mode 100644 index 5c1f6ea7..00000000 --- a/sample_project/Palettes/Dirt.npal +++ /dev/null @@ -1 +0,0 @@ -ûÿ³Ö \ No newline at end of file diff --git a/sample_project/TileSheets/Charset.ng b/sample_project/TileSheets/Charset.ng index 30c425673da58d334218dd8d1f97c8d7f4b92bb2..8e007bfa268ca08646b64a769151c20d62610cfe 100644 GIT binary patch delta 68 zcmbQlHjPb7pMyt$QBOZ0F(A#v8Xt;L@%!(F^8d1JZBXcgZX&(aS4H%wb?;QebdkWME?8bYNg$YEWR{U{U}AAi>1M F1OP)P32XoW diff --git a/src/nostalgia/core/gfx.hpp b/src/nostalgia/core/gfx.hpp index 04121680..3a295e76 100644 --- a/src/nostalgia/core/gfx.hpp +++ b/src/nostalgia/core/gfx.hpp @@ -34,8 +34,11 @@ struct NostalgiaPalette { }; struct NostalgiaGraphic { - static constexpr auto Fields = 4; - uint8_t bpp = 0; + static constexpr auto Fields = 6; + int8_t bpp = 0; + // rows and columns are really only used by TileSheetEditor + int rows = 1; + int columns = 1; ox::FileAddress defaultPalette; NostalgiaPalette pal; ox::Vector tiles; @@ -52,6 +55,8 @@ template ox::Error model(T *io, NostalgiaGraphic *ng) { io->setTypeInfo("nostalgia::core::NostalgiaGraphic", NostalgiaGraphic::Fields); oxReturnError(io->field("bpp", &ng->bpp)); + oxReturnError(io->field("rows", &ng->rows)); + oxReturnError(io->field("columns", &ng->columns)); oxReturnError(io->field("defaultPalette", &ng->defaultPalette)); oxReturnError(io->field("pal", &ng->pal)); oxReturnError(io->field("tiles", &ng->tiles)); diff --git a/src/nostalgia/core/studio/TileSheetEditor.qml b/src/nostalgia/core/studio/TileSheetEditor.qml index ff735b01..7ec09331 100644 --- a/src/nostalgia/core/studio/TileSheetEditor.qml +++ b/src/nostalgia/core/studio/TileSheetEditor.qml @@ -104,6 +104,10 @@ Rectangle { height: tileGrid.rows * tileGrid.baseTileSize * tileGrid.scaleFactor rows: sheetData ? sheetData.rows : 1 columns: sheetData ? sheetData.columns : 1 + states: State { + name: "widthChanged" + PropertyChanges { target: tileGrid.width; width: tileGrid.columns * tileGrid.baseTileSize * tileGrid.scaleFactor } + } Repeater { model: tileGrid.rows * tileGrid.columns Tile { diff --git a/src/nostalgia/core/studio/consts.hpp b/src/nostalgia/core/studio/consts.hpp index 1aa1b904..4361307b 100644 --- a/src/nostalgia/core/studio/consts.hpp +++ b/src/nostalgia/core/studio/consts.hpp @@ -15,6 +15,7 @@ constexpr auto PluginName = "NostalgiaCore"; // Command IDs to use with QUndoCommand::id() enum class CommandId { UpdatePixel = 1, + UpdateDimension = 2, }; } diff --git a/src/nostalgia/core/studio/tilesheeteditor.cpp b/src/nostalgia/core/studio/tilesheeteditor.cpp index f6711ea2..c234b625 100644 --- a/src/nostalgia/core/studio/tilesheeteditor.cpp +++ b/src/nostalgia/core/studio/tilesheeteditor.cpp @@ -10,14 +10,17 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include #include +#include #include "consts.hpp" #include "tilesheeteditor.hpp" @@ -53,6 +56,59 @@ struct LabeledSpinner: public QWidget { }; +class UpdateDimensionsCommand: public QUndoCommand { + public: + enum class Dimension { + Rows, + Columns, + }; + + private: + Dimension m_dimension = Dimension::Rows; + int m_oldVal = 0; + int m_newVal = 0; + SheetData *m_sheetData = nullptr; + + public: + UpdateDimensionsCommand(SheetData *sheetData, Dimension dim, int oldVal, int newVal) { + m_sheetData = sheetData; + m_dimension = dim; + m_newVal = newVal; + m_oldVal = oldVal; + setObsolete(newVal == oldVal); + } + + virtual ~UpdateDimensionsCommand() = default; + + int id() const override { + return static_cast(CommandId::UpdateDimension); + } + + void redo() override { + switch (m_dimension) { + case Dimension::Rows: + m_sheetData->setRows(m_newVal); + break; + case Dimension::Columns: + m_sheetData->setColumns(m_newVal); + break; + } + } + + void undo() override { + switch (m_dimension) { + case Dimension::Rows: + m_sheetData->setRows(m_oldVal); + break; + case Dimension::Columns: + m_sheetData->setColumns(m_oldVal); + break; + } + } + +}; + + class UpdatePixelsCommand: public QUndoCommand { private: struct PixelUpdate { @@ -96,8 +152,8 @@ class UpdatePixelsCommand: public QUndoCommand { } bool mergeWith(const QUndoCommand *cmd) override { - auto other = static_cast(cmd); - if (m_cmdIdx == other->m_cmdIdx) { + auto other = dynamic_cast(cmd); + if (other && m_cmdIdx == other->m_cmdIdx) { m_pixelUpdates.unite(other->m_pixelUpdates); return true; } @@ -107,7 +163,14 @@ class UpdatePixelsCommand: public QUndoCommand { void redo() override { for (const auto &pu : m_pixelUpdates) { pu.item->setProperty("color", m_palette[m_newColorId]); - m_pixels[pu.item->property("pixelNumber").toInt()] = m_newColorId; + const auto index = pu.item->property("pixelNumber").toInt(); + // resize to appropriate number of tiles if pixel beyond current + // range + if (m_pixels.size() < index) { + auto sz = (index / 64 + 1) * 64; + m_pixels.resize(sz); + } + m_pixels[index] = m_newColorId; } } @@ -117,6 +180,7 @@ class UpdatePixelsCommand: public QUndoCommand { m_pixels[pu.item->property("pixelNumber").toInt()] = pu.oldColorId; } } + }; @@ -145,19 +209,49 @@ QStringList SheetData::palette() { return m_palette; } -void SheetData::updatePixels(const studio::Context *ctx, QString ngPath, QString palPath) { +void SheetData::load(const studio::Context *ctx, QString ngPath, QString palPath) { auto ng = ctx->project->loadObj(ngPath); - std::unique_ptr npal; if (palPath == "" && ng->defaultPalette.type() == ox::FileAddressType::Path) { palPath = ng->defaultPalette.getPath().value; } + m_tilesheetPath = ngPath; + m_currentPalettePath = palPath; + setRows(ng->rows); + setColumns(ng->columns); + if (palPath != "") { + setPalette(ctx, palPath); + } else { + setPalette(&ng->pal); + } + updatePixels(ng.get()); +} + +void SheetData::save(const studio::Context *ctx, QString ngPath) { + auto ng = toNostalgiaGraphic(); + ctx->project->writeObj(ngPath, ng.get()); +} + +void SheetData::setPalette(const NostalgiaPalette *npal) { + // load palette + m_palette.clear(); + for (std::size_t i = 0; i < npal->colors.size(); i++) { + const auto c = toQColor(npal->colors[i]); + const auto color = c.name(QColor::HexArgb); + m_palette.append(color); + } +} + +void SheetData::setPalette(const studio::Context *ctx, QString palPath) { + std::unique_ptr npal; try { npal = ctx->project->loadObj(palPath); qInfo() << "Opened palette" << palPath; } catch (ox::Error) { qWarning() << "Could not open palette" << palPath; } - updatePixels(ng.get(), npal.get()); + if (npal) { + setPalette(npal.get()); + } } void SheetData::setSelectedColor(int index) { @@ -171,25 +265,24 @@ QUndoStack *SheetData::undoStack() { void SheetData::setColumns(int columns) { m_columns = columns; emit columnsChanged(columns); + emit changeOccurred(); } void SheetData::setRows(int rows) { m_rows = rows; emit rowsChanged(rows); + emit changeOccurred(); } -void SheetData::updatePixels(const NostalgiaGraphic *ng, const NostalgiaPalette *npal) { - if (!npal) { - npal = &ng->pal; - } +void SheetData::updateColumns(int columns) { + m_cmdStack.push(new UpdateDimensionsCommand(this, UpdateDimensionsCommand::Dimension::Columns, m_columns, columns)); +} - // load palette - for (std::size_t i = 0; i < npal->colors.size(); i++) { - const auto c = toQColor(npal->colors[i]); - const auto color = c.name(QColor::HexArgb); - m_palette.append(color); - } +void SheetData::updateRows(int rows) { + m_cmdStack.push(new UpdateDimensionsCommand(this, UpdateDimensionsCommand::Dimension::Rows, m_rows, rows)); +} +void SheetData::updatePixels(const NostalgiaGraphic *ng) { if (ng->bpp == 8) { for (std::size_t i = 0; i < ng->tiles.size(); i++) { m_pixels.push_back(ng->tiles[i]); @@ -209,6 +302,31 @@ void SheetData::updatePixels(const NostalgiaGraphic *ng, const NostalgiaPalette emit paletteChanged(); } +std::unique_ptr SheetData::toNostalgiaGraphic() { + auto ng = std::make_unique(); + const auto highestColorIdx = static_cast(*std::max_element(m_pixels.begin(), m_pixels.end())); + ng->defaultPalette = m_currentPalettePath.toUtf8().data(); + ng->bpp = highestColorIdx > 15 ? 8 : 4; + ng->columns = m_columns; + ng->rows = m_rows; + if (ng->bpp == 4) { + ng->tiles.resize(m_pixels.size() / 2); + for (int i = 0; i < m_pixels.size(); ++i) { + if (i & 1) { + ng->tiles[i / 2] |= static_cast(m_pixels[i]) << 4; + } else { + ng->tiles[i / 2] = static_cast(m_pixels[i]); + } + } + } else { + ng->tiles.resize(m_pixels.size()); + for (int i = 0; i < m_pixels.size(); ++i) { + ng->tiles[i] = static_cast(m_pixels[i]); + } + } + return ng; +} + void SheetData::beginCmd() { ++m_cmdIdx; } @@ -220,6 +338,7 @@ void SheetData::endCmd() { TileSheetEditor::TileSheetEditor(QString path, const studio::Context *ctx, QWidget *parent): studio::Editor(parent) { m_ctx = ctx; + m_itemPath = path; m_itemName = path.mid(path.lastIndexOf('/')); auto lyt = new QVBoxLayout(this); m_splitter = new QSplitter(this); @@ -227,12 +346,29 @@ TileSheetEditor::TileSheetEditor(QString path, const studio::Context *ctx, QWidg auto canvasLyt = new QVBoxLayout(canvasParent); m_canvas = new QQuickWidget(canvasParent); canvasLyt->addWidget(m_canvas); - canvasLyt->setMenuBar(setupToolBar()); + auto tb = new QToolBar(tr("Tile Sheet Options")); + m_tilesX = new LabeledSpinner(tr("Tiles &X:"), 1, m_sheetData.columns()); + m_tilesY = new LabeledSpinner(tr("Tiles &Y:"), 1, m_sheetData.rows()); + tb->addWidget(m_tilesX); + tb->addWidget(m_tilesY); + canvasLyt->setMenuBar(tb); lyt->addWidget(m_splitter); m_splitter->addWidget(canvasParent); m_splitter->addWidget(setupColorPicker(m_splitter)); m_splitter->setStretchFactor(0, 1); - m_sheetData.updatePixels(m_ctx, path); + connect(&m_sheetData, &SheetData::columnsChanged, [this](int val) { + disconnect(m_tilesX->spinBox, QOverload::of(&QSpinBox::valueChanged), &m_sheetData, &SheetData::updateColumns); + m_tilesX->spinBox->setValue(val); + connect(m_tilesX->spinBox, QOverload::of(&QSpinBox::valueChanged), &m_sheetData, &SheetData::updateColumns); + }); + connect(&m_sheetData, &SheetData::rowsChanged, [this](int val) { + disconnect(m_tilesY->spinBox, QOverload::of(&QSpinBox::valueChanged), &m_sheetData, &SheetData::updateRows); + m_tilesY->spinBox->setValue(val); + connect(m_tilesY->spinBox, QOverload::of(&QSpinBox::valueChanged), &m_sheetData, &SheetData::updateRows); + }); + m_sheetData.load(m_ctx, m_itemPath); + connect(m_tilesX->spinBox, QOverload::of(&QSpinBox::valueChanged), &m_sheetData, &SheetData::updateColumns); + connect(m_tilesY->spinBox, QOverload::of(&QSpinBox::valueChanged), &m_sheetData, &SheetData::updateRows); m_canvas->rootContext()->setContextProperty("sheetData", &m_sheetData); m_canvas->setSource(QUrl::fromLocalFile(":/qml/TileSheetEditor.qml")); m_canvas->setResizeMode(QQuickWidget::SizeRootObjectToView); @@ -249,24 +385,12 @@ QString TileSheetEditor::itemName() { return m_itemName; } -void TileSheetEditor::saveItem() { -} - QUndoStack *TileSheetEditor::undoStack() { return m_sheetData.undoStack(); } -QWidget *TileSheetEditor::setupToolBar() { - auto tb = new QToolBar(tr("Tile Sheet Options")); - m_tilesX = new LabeledSpinner(tr("Tiles &X:"), 1, m_sheetData.columns()); - m_tilesY = new LabeledSpinner(tr("Tiles &Y:"), 1, m_sheetData.rows()); - tb->addWidget(m_tilesX); - tb->addWidget(m_tilesY); - connect(&m_sheetData, &SheetData::columnsChanged, m_tilesX->spinBox, &QSpinBox::setValue); - connect(&m_sheetData, &SheetData::rowsChanged, m_tilesY->spinBox, &QSpinBox::setValue); - connect(m_tilesX->spinBox, QOverload::of(&QSpinBox::valueChanged), &m_sheetData, &SheetData::setColumns); - connect(m_tilesY->spinBox, QOverload::of(&QSpinBox::valueChanged), &m_sheetData, &SheetData::setRows); - return tb; +void TileSheetEditor::saveItem() { + m_sheetData.save(m_ctx, m_itemPath); } QWidget *TileSheetEditor::setupColorPicker(QWidget *parent) { @@ -314,8 +438,6 @@ void TileSheetEditor::restoreState() { QSettings settings(m_ctx->orgName, PluginName); settings.beginGroup("TileSheetEditor/" + m_itemName); m_splitter->restoreState(settings.value("m_splitter/state", m_splitter->saveState()).toByteArray()); - m_sheetData.setRows(settings.value("m_sheetData/tileRows", 1).toInt()); - m_sheetData.setColumns(settings.value("m_sheetData/tileColumns", 1).toInt()); m_colorPicker.colorTable->horizontalHeader()->restoreState(settings.value("m_colorPicker.colorTable/geometry", m_colorPicker.colorTable->horizontalHeader()->saveState()).toByteArray()); settings.endGroup(); } diff --git a/src/nostalgia/core/studio/tilesheeteditor.hpp b/src/nostalgia/core/studio/tilesheeteditor.hpp index 613600a8..4666c88c 100644 --- a/src/nostalgia/core/studio/tilesheeteditor.hpp +++ b/src/nostalgia/core/studio/tilesheeteditor.hpp @@ -8,14 +8,9 @@ #pragma once -#include -#include #include -#include -#include #include #include -#include #include #include @@ -30,7 +25,9 @@ class SheetData: public QObject { Q_PROPERTY(QStringList palette READ palette NOTIFY paletteChanged) private: - QQuickItem *m_prevPixelUpdated = nullptr; + class QQuickItem *m_prevPixelUpdated = nullptr; + QString m_tilesheetPath; + QString m_currentPalettePath; uint64_t m_cmdIdx = 0; QUndoStack m_cmdStack; QStringList m_palette; @@ -54,7 +51,13 @@ class SheetData: public QObject { QStringList palette(); - void updatePixels(const studio::Context *ctx, QString ngPath, QString palPath = ""); + void load(const studio::Context *ctx, QString ngPath, QString palPath = ""); + + void save(const studio::Context *ctx, QString ngPath); + + void setPalette(const NostalgiaPalette *pal); + + void setPalette(const studio::Context *ctx, QString palPath); void setSelectedColor(int index); @@ -65,8 +68,20 @@ class SheetData: public QObject { void setRows(int rows); + /** + * Sets columns through a QUndoCommand. + */ + void updateColumns(int columns); + + /** + * Sets rows through a QUndoCommand. + */ + void updateRows(int rows); + private: - void updatePixels(const NostalgiaGraphic *ng, const NostalgiaPalette *npal); + void updatePixels(const NostalgiaGraphic *ng); + + [[nodiscard]] std::unique_ptr toNostalgiaGraphic(); signals: void changeOccurred(); @@ -86,16 +101,17 @@ class TileSheetEditor: public studio::Editor { Q_OBJECT private: + QString m_itemPath; QString m_itemName; const studio::Context *m_ctx = nullptr; SheetData m_sheetData; - QSplitter *m_splitter = nullptr; + class QSplitter *m_splitter = nullptr; struct LabeledSpinner *m_tilesX = nullptr; struct LabeledSpinner *m_tilesY = nullptr; class QQuickWidget* m_canvas = nullptr; struct { QComboBox *palette = nullptr; - QTableWidget *colorTable = nullptr; + class QTableWidget *colorTable = nullptr; } m_colorPicker; public: @@ -105,13 +121,12 @@ class TileSheetEditor: public studio::Editor { QString itemName() override; - void saveItem() override; - QUndoStack *undoStack() override; - private: - QWidget *setupToolBar(); + protected: + void saveItem() override; + private: QWidget *setupColorPicker(QWidget *widget); void setColorTable(QStringList hexColors);