[nostalgia/core/studio] Add select and cut/copy/paste to TileSheetEditor

This commit is contained in:
Gary Talent 2020-10-20 22:14:56 -05:00
parent 985b2b57ba
commit 2580dbb7ab
7 changed files with 447 additions and 69 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2016 - 2019 gtalent2@gmail.com
* Copyright 2016 - 2020 gtalent2@gmail.com
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
@ -27,4 +27,12 @@ Rectangle {
anchors.bottom: pixel.bottom
}
Rectangle {
visible: sheetData ? sheetData.pixelSelected[pixel.pixelNumber] & 1 == 1 : false
color: '#0088ff'
opacity: 0.5
width: parent.width
height: parent.height
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2016 - 2019 gtalent2@gmail.com
* Copyright 2016 - 2020 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
@ -34,10 +34,21 @@ Rectangle {
onPressed: {
if (mouse.button === Qt.LeftButton && !contextMenu.visible) {
var pixel = pixelAt(mouseX, mouseY);
if (pixel) {
sheetData.beginCmd();
sheetData.updatePixel(pixel);
switch (sheetData.activeTool) {
case 'Draw':
var pixel = pixelAt(mouseX, mouseY);
if (pixel) {
sheetData.beginCmd();
sheetData.updatePixel(pixel);
}
break;
case 'Select':
var pixel = pixelAt(mouseX, mouseY);
if (pixel) {
sheetData.beginCmd();
sheetData.selectPixel(pixel);
}
break;
}
}
}
@ -112,7 +123,19 @@ Rectangle {
onPositionChanged: {
if (mouseArea.pressedButtons & Qt.LeftButton && !contextMenu.visible) {
sheetData.updatePixel(pixelAt(mouseX, mouseY));
var pixel = pixelAt(mouseX, mouseY);
switch (sheetData.activeTool) {
case 'Draw':
if (pixel) {
sheetData.updatePixel(pixel);
}
break;
case 'Select':
if (pixel) {
sheetData.selectPixel(pixel);
}
break;
}
}
}

View File

@ -14,12 +14,4 @@ constexpr auto PluginName = "NostalgiaCore";
constexpr auto TileSheetDir = "/TileSheets/";
constexpr auto PaletteDir = "/Palettes/";
// Command IDs to use with QUndoCommand::id()
enum class CommandId {
UpdatePixel = 1,
ModPixel = 2,
UpdateDimension = 3,
InsertTile = 4,
};
}

View File

@ -26,16 +26,6 @@ namespace {
return (a << 15) | (r << 10) | (g << 5) | (b << 0);
}
[[nodiscard]] int pointToIdx(int w, int x, int y) {
constexpr auto colLength = PixelsPerTile;
const auto rowLength = (w / TileWidth) * colLength;
const auto colStart = colLength * (x / TileWidth);
const auto rowStart = rowLength * (y / TileHeight);
const auto colOffset = x % TileWidth;
const auto rowOffset = (y % TileHeight) * TileHeight;
return colStart + colOffset + rowStart + rowOffset;
}
}
[[nodiscard]] static int countColors(const QImage &img, int argTiles) {

View File

@ -6,6 +6,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <QString>
#include <ox/std/error.hpp>
@ -17,6 +19,16 @@
namespace nostalgia::core {
[[nodiscard]] constexpr int pointToIdx(int w, int x, int y) noexcept {
constexpr auto colLength = PixelsPerTile;
const auto rowLength = (w / TileWidth) * colLength;
const auto colStart = colLength * (x / TileWidth);
const auto rowStart = rowLength * (y / TileHeight);
const auto colOffset = x % TileWidth;
const auto rowOffset = (y % TileHeight) * TileHeight;
return colStart + colOffset + rowStart + rowOffset;
}
template<typename T>
ox::Result<std::vector<uint8_t>> toBuffer(T *data, std::size_t buffSize = ox::units::MB) {
std::vector<uint8_t> buff(buffSize);

View File

@ -1,14 +1,13 @@
/*
* Copyright 2016 - 2019 gtalent2@gmail.com
* Copyright 2016 - 2020 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <array>
#include <memory>
#include <QBuffer>
#include <QDialog>
#include <QFormLayout>
#include <QHBoxLayout>
@ -23,22 +22,52 @@
#include <QSettings>
#include <QSpinBox>
#include <QSplitter>
#include <QUndoCommand>
#include <QTableWidget>
#include <QToolBar>
#include <QUndoCommand>
#include <QVBoxLayout>
#include <memory>
#include <qnamespace.h>
#include <nostalgia/core/consts.hpp>
#include <nostalgia/common/point.hpp>
#include <qnamespace.h>
#include "consts.hpp"
#include "imgconv.hpp"
#include "util.hpp"
#include "tilesheeteditor.hpp"
namespace nostalgia::core {
[[nodiscard]] constexpr int ptToIdx(int x, int y, int c) noexcept {
return pointToIdx(c * TileWidth, x, y);
}
[[nodiscard]] constexpr int ptToIdx(common::Point pt, int c) noexcept {
return pointToIdx(c * TileWidth, pt.x, pt.y);
}
[[nodiscard]] constexpr common::Point idxToPt(int i, int c) noexcept {
const auto t = i / PixelsPerTile; // tile number
const auto iti = i % PixelsPerTile; // in tile index
const auto tc = t % c; // tile column
const auto tr = t / c; // tile row
const auto itx = iti % TileWidth; // in tile x
const auto ity = iti / TileHeight; // in tile y
return {
itx + tc * TileWidth,
ity + tr * TileHeight,
};
}
static_assert(idxToPt(4, 1) == common::Point{4, 0});
static_assert(idxToPt(8, 1) == common::Point{0, 1});
static_assert(idxToPt(8, 2) == common::Point{0, 1});
static_assert(idxToPt(64, 2) == common::Point{8, 0});
static_assert(idxToPt(128, 2) == common::Point{0, 8});
static_assert(idxToPt(129, 2) == common::Point{1, 8});
static_assert(idxToPt(192, 2) == common::Point{8, 8});
static_assert(idxToPt(384, 8) == common::Point{48, 0});
class ModAfterDialog: public QDialog {
private:
@ -57,7 +86,8 @@ class ModAfterDialog: public QDialog {
}
m_mod->setMinimum(-range + 1);
m_mod->setMaximum(range - 1);
auto lyt = new QFormLayout(this); lyt->addRow(tr("&Greater/Equal To:"), m_afterColor);
auto lyt = new QFormLayout(this);
lyt->addRow(tr("&Greater/Equal To:"), m_afterColor);
lyt->addRow(tr("&Mod By:"), m_mod);
lyt->addWidget(okBtn);
connect(okBtn, &QPushButton::clicked, this, &ModAfterDialog::accept);
@ -149,10 +179,9 @@ class ModPixelsCommand: public QUndoCommand {
private:
SheetData *m_sheetData = nullptr;
QHash<int, int> m_pixelUpdates;
int m_mod = 0;
public:
ModPixelsCommand(SheetData *sheetData, int mod): m_sheetData(sheetData), m_mod(mod) {
ModPixelsCommand(SheetData *sheetData): m_sheetData(sheetData) {
}
virtual ~ModPixelsCommand() = default;
@ -198,10 +227,11 @@ class UpdatePixelsCommand: public QUndoCommand {
int m_newColorId = 0;
const QStringList &m_palette;
QVector<int> &m_pixels;
QVector<int> &m_pixelSelected;
QSet<PixelUpdate> m_pixelUpdates;
public:
UpdatePixelsCommand(QVector<int> &pixels, const QStringList &palette, QQuickItem *pixelItem, int newColorId, uint64_t cmdIdx): m_palette(palette), m_pixels(pixels) {
UpdatePixelsCommand(QVector<int> &pixels, QVector<int> &pixelSelected, const QStringList &palette, QQuickItem *pixelItem, int newColorId, uint64_t cmdIdx): m_palette(palette), m_pixels(pixels), m_pixelSelected(pixelSelected) {
m_newColorId = newColorId;
m_cmdIdx = cmdIdx;
PixelUpdate pu;
@ -235,6 +265,7 @@ class UpdatePixelsCommand: public QUndoCommand {
if (m_pixels.size() < index) {
auto sz = (index / 64 + 1) * 64;
m_pixels.resize(sz);
m_pixelSelected.resize(sz);
}
m_pixels[index] = m_newColorId;
}
@ -288,6 +319,86 @@ class InsertTileCommand: public QUndoCommand {
};
class PasteClipboardCommand: public QUndoCommand {
private:
SheetData *m_sheetData = nullptr;
TileSheetClipboard m_restore;
TileSheetClipboard m_apply;
public:
PasteClipboardCommand(SheetData *sheetData,
const TileSheetClipboard &restore,
const TileSheetClipboard &apply): m_sheetData(sheetData), m_restore(restore), m_apply(apply) {
}
virtual ~PasteClipboardCommand() = default;
int id() const override {
return static_cast<int>(CommandId::ClipboardPaste);
}
void redo() override {
m_sheetData->applyClipboard(m_apply);
}
void undo() override {
m_sheetData->applyClipboard(m_restore);
}
};
void TileSheetClipboard::add(int idx, int color) {
if (m_chunks.size() && m_chunks.back().index + m_chunks.back().size == idx) {
++m_chunks.back().size;
} else {
m_chunks.push_back({idx, 1});
}
m_pixels.push_back(color);
}
void TileSheetClipboard::clear() {
m_chunks.clear();
m_pixels.clear();
}
bool TileSheetClipboard::empty() const {
return m_pixels.empty();
}
void TileSheetClipboard::paste(int targetIdx, QVector<int> *pixels) const {
std::size_t ci = 0; // clipboard index
// set prevSrcIdx to current source index, as first iteration is already
// correct
int prevSrcIdx = m_chunks.size() ? m_chunks[0].index : 0;
for (std::size_t chunkIdx = 0; chunkIdx < m_chunks.size(); ++chunkIdx) {
const auto &chunk = m_chunks[chunkIdx];
targetIdx += chunk.index - prevSrcIdx;
prevSrcIdx = chunk.index;
const auto targetMod = targetIdx - chunk.index;
for (int i = 0; i < chunk.size; ++i) {
const auto ti = chunk.index + i + targetMod; // target index
if (ti < pixels->size()) {
(*pixels)[ti] = m_pixels[ci];
}
++ci;
}
}
}
void TileSheetClipboard::setPoints(common::Point p1, common::Point p2) {
m_p1 = p1;
m_p2 = p2;
}
common::Point TileSheetClipboard::point1() const {
return m_p1;
}
common::Point TileSheetClipboard::point2() const {
return m_p2;
}
void TileSheetEditorColorTableDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opt, const QModelIndex &idx) const {
if (idx.column() != 1) {
@ -302,21 +413,37 @@ void TileSheetEditorColorTableDelegate::paint(QPainter *painter, const QStyleOpt
SheetData::SheetData(QUndoStack *undoStack): m_cmdStack(undoStack) {
}
void SheetData::selectPixel(QVariant pixelItem) {
auto p = qobject_cast<QQuickItem*>(pixelItem.value<QObject*>());
if (p && p != m_prevPixelOperand) {
const auto index = p->property("pixelNumber").toInt();
const auto point = idxToPt(index, m_columns);
m_prevPixelOperand = p;
if (m_selectionStart == common::Point{-1, -1}) {
m_selectionStart = point;
}
markSelection(point);
}
}
void SheetData::updatePixel(QVariant pixelItem) {
auto p = qobject_cast<QQuickItem*>(pixelItem.value<QObject*>());
if (p && p != m_prevPixelUpdated) {
m_cmdStack->push(new UpdatePixelsCommand(m_pixels, m_palette, p, m_selectedColor, m_cmdIdx));
m_prevPixelUpdated = p;
if (p && p != m_prevPixelOperand) {
m_cmdStack->push(new UpdatePixelsCommand(m_pixels, m_pixelSelected, m_palette, p, m_selectedColor, m_cmdIdx));
m_prevPixelOperand = p;
emit changeOccurred();
}
}
void SheetData::beginCmd() {
++m_cmdIdx;
m_selectionStart = {-1, -1};
m_selectionEnd = {-1, -1};
emit pixelSelectedChanged(0);
}
void SheetData::endCmd() {
m_prevPixelUpdated = nullptr;
m_prevPixelOperand = nullptr;
}
void SheetData::insertTileCmd(int tileIdx) {
@ -335,6 +462,10 @@ int SheetData::rows() const {
return m_rows;
}
const QVector<int> &SheetData::pixelSelected() const {
return m_pixelSelected;
}
const QVector<int> &SheetData::pixels() const {
return m_pixels;
}
@ -401,6 +532,7 @@ void SheetData::setPalette(const studio::Context *ctx, QString palPath) {
void SheetData::insertTile(int tileIdx, QVector<int> tileData) {
auto pxIdx = tileIdx * PixelsPerTile;
m_pixels.insert(pxIdx, PixelsPerTile, 0);
m_pixelSelected.insert(pxIdx, PixelsPerTile, 0);
std::copy(tileData.begin(), tileData.end(), &m_pixels[pxIdx]);
emit pixelsChanged();
emit changeOccurred();
@ -413,6 +545,7 @@ QVector<int> SheetData::deleteTile(int tileIdx) {
m_pixels.begin() + (pxIdx + PixelsPerTile),
std::back_inserter(out));
m_pixels.remove(pxIdx, PixelsPerTile);
m_pixelSelected.remove(pxIdx, PixelsPerTile);
emit pixelsChanged();
emit changeOccurred();
return out;
@ -423,7 +556,7 @@ void SheetData::setSelectedColor(int index) {
}
void SheetData::modGteCmd(int color, int mod) {
auto cmd = new ModPixelsCommand(this, mod);
auto cmd = new ModPixelsCommand(this);
for (int i = 0; i < m_pixels.size(); ++i) {
const auto p = m_pixels[i];
if (p >= color) {
@ -469,6 +602,79 @@ std::unique_ptr<NostalgiaGraphic> SheetData::toNostalgiaGraphic() const {
return ng;
}
int SheetData::activeTool() const {
return static_cast<int>(m_activeTool);
}
bool SheetData::clipboardEmpty() const {
return m_clipboard.empty();
}
void SheetData::cutToClipboard() {
m_clipboard.setPoints(m_selectionStart, m_selectionEnd);
cutToClipboard(&m_clipboard);
}
void SheetData::cutToClipboard(TileSheetClipboard *cb) {
const auto start = ptToIdx(cb->point1(), m_columns);
const auto end = ptToIdx(cb->point2(), m_columns);
for (int i = start; i <= end; ++i) {
const auto s = m_pixelSelected[i];
if (s) {
cb->add(i, m_pixels[i]);
m_pixels[i] = 0;
}
}
emit pixelsChanged();
emit changeOccurred();
}
void SheetData::copyToClipboard() {
m_clipboard.setPoints(m_selectionStart, m_selectionEnd);
copyToClipboard(&m_clipboard);
}
void SheetData::copyToClipboard(TileSheetClipboard *cb) {
const auto start = ptToIdx(cb->point1(), m_columns);
const auto end = ptToIdx(cb->point2(), m_columns);
for (int i = start; i <= end; ++i) {
const auto s = m_pixelSelected[i];
if (s) {
cb->add(i, m_pixels[i]);
}
}
}
void SheetData::pasteClipboard() {
TileSheetClipboard apply = m_clipboard;
TileSheetClipboard restore;
const auto p2 = m_selectionStart + (apply.point2() - apply.point1());
apply.setPoints(m_selectionStart, p2);
restore.setPoints(m_selectionStart, p2);
markSelection(p2);
copyToClipboard(&restore);
m_cmdStack->push(new PasteClipboardCommand(this, restore, apply));
}
void SheetData::applyClipboard(const TileSheetClipboard &cb) {
const auto idx = ptToIdx(cb.point1(), m_columns);
cb.paste(idx, &m_pixels);
emit pixelsChanged();
emit changeOccurred();
}
void SheetData::markSelection(common::Point selectionEnd) {
// mark selection
m_selectionEnd = selectionEnd;
ox::memsetElements(m_pixelSelected.data(), 0, static_cast<std::size_t>(m_pixelSelected.size()));
for (auto x = m_selectionStart.x; x <= m_selectionEnd.x; ++x) {
for (auto y = m_selectionStart.y; y <= m_selectionEnd.y; ++y) {
m_pixelSelected[ptToIdx(x, y, m_columns)] |= 1;
}
}
emit pixelSelectedChanged((m_selectionEnd.x - m_selectionStart.x + 1) * (m_selectionEnd.y - m_selectionStart.y + 1));
}
void SheetData::setColumns(int columns) {
m_columns = columns;
emit columnsChanged(columns);
@ -489,11 +695,19 @@ void SheetData::updateRows(int rows) {
m_cmdStack->push(new UpdateDimensionsCommand(this, UpdateDimensionsCommand::Dimension::Rows, m_rows, rows));
}
void SheetData::setActiveTool(int t) {
m_activeTool = static_cast<TileSheetTool>(t);
ox::memsetElements(m_pixelSelected.data(), 0, static_cast<std::size_t>(m_pixelSelected.size()));
emit pixelSelectedChanged(0);
}
void SheetData::updatePixels(const NostalgiaGraphic *ng) {
m_pixels.clear();
m_pixelSelected.clear();
if (ng->bpp == 8) {
for (std::size_t i = 0; i < ng->tiles.size(); i++) {
m_pixels.push_back(ng->tiles[i]);
m_pixelSelected.push_back(0);
}
} else {
for (std::size_t i = 0; i < ng->tiles.size() * 2; i++) {
@ -504,11 +718,16 @@ void SheetData::updatePixels(const NostalgiaGraphic *ng) {
p = ng->tiles[i / 2] & 0xF;
}
m_pixels.push_back(p);
m_pixelSelected.push_back(0);
}
}
emit pixelsChanged();
}
const char *SheetData::activeToolString() const {
return toString(m_activeTool);
}
TileSheetEditor::TileSheetEditor(QString path, const studio::Context *ctx, QWidget *parent): studio::Editor(parent), m_sheetData(undoStack()) {
m_ctx = ctx;
m_itemPath = path;
@ -522,10 +741,21 @@ TileSheetEditor::TileSheetEditor(QString path, const studio::Context *ctx, QWidg
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());
m_updateAfterBtn = new QPushButton(tr("&Mod After"), tb);
auto updateAfterBtn = new QPushButton(tr("&Mod GTE"), tb);
m_toolBtns.addButton(new QPushButton(tr("&Select"), tb), static_cast<int>(TileSheetTool::Select));
m_toolBtns.addButton(new QPushButton(tr("&Draw"), tb), static_cast<int>(TileSheetTool::Draw));
//m_toolBtns.addButton(new QPushButton(tr("F&ill"), tb), static_cast<int>(TileSheetTool::Fill));
tb->addWidget(m_tilesX);
tb->addWidget(m_tilesY);
tb->addWidget(m_updateAfterBtn);
tb->addSeparator();
tb->addWidget(updateAfterBtn);
tb->addSeparator();
for (auto btn : m_toolBtns.buttons()) {
btn->setCheckable(true);
tb->addWidget(btn);
}
m_toolBtns.button(m_sheetData.activeTool())->setChecked(true);
connect(&m_toolBtns, &QButtonGroup::idClicked, &m_sheetData, &SheetData::setActiveTool);
canvasLyt->setMenuBar(tb);
lyt->addWidget(m_splitter);
m_splitter->addWidget(canvasParent);
@ -544,7 +774,7 @@ TileSheetEditor::TileSheetEditor(QString path, const studio::Context *ctx, QWidg
m_sheetData.load(m_ctx, m_itemPath);
connect(m_tilesX->spinBox, QOverload<int>::of(&QSpinBox::valueChanged), &m_sheetData, &SheetData::updateColumns);
connect(m_tilesY->spinBox, QOverload<int>::of(&QSpinBox::valueChanged), &m_sheetData, &SheetData::updateRows);
connect(m_updateAfterBtn, &QPushButton::clicked, this, &TileSheetEditor::updateAfterClicked);
connect(updateAfterBtn, &QPushButton::clicked, this, &TileSheetEditor::updateAfterClicked);
m_canvas->rootContext()->setContextProperty("sheetData", &m_sheetData);
m_canvas->setSource(QUrl::fromLocalFile(":/qml/TileSheetEditor.qml"));
m_canvas->setResizeMode(QQuickWidget::SizeRootObjectToView);
@ -553,6 +783,11 @@ TileSheetEditor::TileSheetEditor(QString path, const studio::Context *ctx, QWidg
restoreState();
connect(&m_sheetData, &SheetData::changeOccurred, [this] { setUnsavedChanges(true); });
connect(&m_sheetData, &SheetData::paletteChanged, this, &TileSheetEditor::setColorTable);
connect(&m_sheetData, &SheetData::pixelSelectedChanged, [this](int selected) {
setCutEnabled(selected);
setCopyEnabled(selected);
setPasteEnabled(selected && !m_sheetData.clipboardEmpty());
});
setExportable(true);
}
@ -584,6 +819,18 @@ void TileSheetEditor::exportFile() {
}
}
void TileSheetEditor::cut() {
m_sheetData.cutToClipboard();
}
void TileSheetEditor::copy() {
m_sheetData.copyToClipboard();
}
void TileSheetEditor::paste() {
m_sheetData.pasteClipboard();
}
void TileSheetEditor::saveItem() {
m_sheetData.save(m_ctx, m_itemPath);
}
@ -657,28 +904,6 @@ QString TileSheetEditor::palettePath(QString paletteName) const {
return PaletteDir + paletteName + FileExt_npal;
}
constexpr common::Point idxToPt(int i, int c) noexcept {
const auto t = i / PixelsPerTile; // tile number
const auto iti = i % PixelsPerTile; // in tile index
const auto tc = t % c; // tile column
const auto tr = t / c; // tile row
const auto itx = iti % TileWidth; // in tile x
const auto ity = iti / TileHeight; // in tile y
return {
itx + tc * TileWidth,
ity + tr * TileHeight,
};
}
static_assert(idxToPt(4, 1) == common::Point{4, 0});
static_assert(idxToPt(8, 1) == common::Point{0, 1});
static_assert(idxToPt(8, 2) == common::Point{0, 1});
static_assert(idxToPt(64, 2) == common::Point{8, 0});
static_assert(idxToPt(128, 2) == common::Point{0, 8});
static_assert(idxToPt(129, 2) == common::Point{1, 8});
static_assert(idxToPt(192, 2) == common::Point{8, 8});
static_assert(idxToPt(384, 8) == common::Point{48, 0});
QImage TileSheetEditor::toQImage(NostalgiaGraphic *ng, NostalgiaPalette *npal) const {
const auto w = ng->columns * TileWidth;
const auto h = ng->rows * TileHeight;
@ -730,7 +955,10 @@ void TileSheetEditor::setColorTable() {
void TileSheetEditor::updateAfterClicked() {
auto d = new ModAfterDialog(m_sheetData.palette(), this);
connect(d, &ModAfterDialog::accepted, [this, d] {
m_sheetData.modGteCmd(d->color(), d->mod());
auto mod = d->mod();
if (mod) {
m_sheetData.modGteCmd(d->color(), mod);
}
});
connect(d, &ModAfterDialog::finished, d, &ModAfterDialog::deleteLater);
d->open();

View File

@ -1,5 +1,5 @@
/*
* Copyright 2016 - 2019 gtalent2@gmail.com
* Copyright 2016 - 2020 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
@ -8,16 +8,100 @@
#pragma once
#include <QButtonGroup>
#include <QStringList>
#include <QStyledItemDelegate>
#include <QUndoStack>
#include <QVariant>
#include <nostalgia/common/bounds.hpp>
#include <nostalgia/core/gfx.hpp>
#include <nostalgia/studio/studio.hpp>
namespace nostalgia::core {
// Command IDs to use with QUndoCommand::id()
enum class CommandId {
UpdatePixel = 1,
ModPixel = 2,
UpdateDimension = 3,
InsertTile = 4,
ClipboardPaste = 4,
};
enum class TileSheetTool: int {
Select,
Draw,
Fill,
};
[[nodiscard]] constexpr auto toString(TileSheetTool t) noexcept {
switch (t) {
case TileSheetTool::Select:
return "Select";
case TileSheetTool::Draw:
return "Draw";
case TileSheetTool::Fill:
return "Fill";
}
return "";
}
struct PixelChunk {
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.studio.PixelChunk";
static constexpr auto Fields = 2;
static constexpr auto TypeVersion = 1;
int index = -1;
int size = 0;
};
template<typename T>
ox::Error model(T *io, PixelChunk *c) {
io->template setTypeInfo<PixelChunk>();
oxReturnError(io->field("index", &c->index));
oxReturnError(io->field("size", &c->size));
return OxError(0);
}
struct TileSheetClipboard {
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.studio.TileSheetClipboard";
static constexpr auto Fields = 2;
static constexpr auto TypeVersion = 1;
template<typename T>
friend ox::Error model(T*, TileSheetClipboard*);
protected:
ox::Vector<PixelChunk> m_chunks;
ox::Vector<int> m_pixels;
common::Point m_p1;
common::Point m_p2;
public:
void add(int idx, int color);
void clear();
[[nodiscard]] bool empty() const;
void paste(int targetIdx, QVector<int> *pixels) const;
void setPoints(common::Point p1, common::Point p2);
[[nodiscard]] common::Point point1() const;
[[nodiscard]] common::Point point2() const;
};
template<typename T>
ox::Error model(T *io, TileSheetClipboard *b) {
io->template setTypeInfo<TileSheetClipboard>();
oxReturnError(io->field("chunks", &b->m_chunks));
oxReturnError(io->field("pixels", &b->m_pixels));
return OxError(0);
}
struct TileSheetEditorColorTableDelegate: public QStyledItemDelegate {
void paint(QPainter *painter, const QStyleOptionViewItem &opt, const QModelIndex &idx) const;
@ -28,24 +112,33 @@ class SheetData: public QObject {
Q_OBJECT
Q_PROPERTY(int columns READ columns WRITE setColumns NOTIFY columnsChanged)
Q_PROPERTY(int rows READ rows WRITE setRows NOTIFY rowsChanged)
Q_PROPERTY(QVector<int> pixelSelected READ pixelSelected NOTIFY pixelSelectedChanged)
Q_PROPERTY(QVector<int> pixels READ pixels NOTIFY pixelsChanged)
Q_PROPERTY(QStringList palette READ palette NOTIFY paletteChanged)
Q_PROPERTY(QString activeTool READ activeToolString)
private:
class QQuickItem *m_prevPixelUpdated = nullptr;
class QQuickItem *m_prevPixelOperand = nullptr;
QString m_tilesheetPath;
QString m_currentPalettePath;
uint64_t m_cmdIdx = 0;
QUndoStack *m_cmdStack;
QStringList m_palette;
QVector<int> m_pixelSelected;
QVector<int> m_pixels;
common::Point m_selectionStart = {-1, -1};
common::Point m_selectionEnd = {-1, -1};
int m_columns = 1;
int m_rows = 1;
int m_selectedColor = 0;
TileSheetTool m_activeTool = TileSheetTool::Draw;
TileSheetClipboard m_clipboard;
public:
SheetData(QUndoStack*);
Q_INVOKABLE void selectPixel(QVariant pixel);
Q_INVOKABLE void updatePixel(QVariant pixel);
Q_INVOKABLE void beginCmd();
@ -60,6 +153,8 @@ class SheetData: public QObject {
[[nodiscard]] int rows() const;
[[nodiscard]] const QVector<int> &pixelSelected() const;
[[nodiscard]] const QVector<int> &pixels() const;
[[nodiscard]] QStringList palette() const;
@ -88,6 +183,24 @@ class SheetData: public QObject {
[[nodiscard]] std::unique_ptr<NostalgiaGraphic> toNostalgiaGraphic() const;
[[nodiscard]] int activeTool() const;
[[nodiscard]] bool clipboardEmpty() const;
void cutToClipboard();
void cutToClipboard(TileSheetClipboard *cb);
void copyToClipboard();
void copyToClipboard(TileSheetClipboard *cb);
void pasteClipboard();
void applyClipboard(const TileSheetClipboard &cb);
void markSelection(common::Point selectionEnd);
public slots:
void setColumns(int columns);
@ -103,9 +216,13 @@ class SheetData: public QObject {
*/
void updateRows(int rows);
void setActiveTool(int t);
private:
void updatePixels(const NostalgiaGraphic *ng);
const char *activeToolString() const;
signals:
void changeOccurred();
@ -113,6 +230,8 @@ class SheetData: public QObject {
void rowsChanged(int);
void pixelSelectedChanged(int pixelsSelected);
void pixelsChanged();
void paletteChanged();
@ -129,10 +248,10 @@ class TileSheetEditor: public studio::Editor {
QString m_itemName;
const studio::Context *m_ctx = nullptr;
SheetData m_sheetData;
QButtonGroup m_toolBtns;
class QSplitter *m_splitter = nullptr;
struct LabeledSpinner *m_tilesX = nullptr;
struct LabeledSpinner *m_tilesY = nullptr;
class QPushButton *m_updateAfterBtn = nullptr;
class QQuickWidget* m_canvas = nullptr;
struct {
QComboBox *palette = nullptr;
@ -148,6 +267,12 @@ class TileSheetEditor: public studio::Editor {
void exportFile() override;
void cut() override;
void copy() override;
void paste() override;
protected:
void saveItem() override;