[nostalgia/core/studio] Add select and cut/copy/paste to TileSheetEditor
This commit is contained in:
parent
985b2b57ba
commit
2580dbb7ab
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user