Files
ox/src/nostalgia/core/studio/tilesheeteditor.cpp
T

740 lines
21 KiB
C++

/*
* Copyright 2016 - 2019 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
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <array>
#include <QBuffer>
#include <QDialog>
#include <QFormLayout>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QPainter>
#include <QPointer>
#include <QPushButton>
#include <QQmlContext>
#include <QQuickItem>
#include <QQuickWidget>
#include <QSet>
#include <QSettings>
#include <QSpinBox>
#include <QSplitter>
#include <QUndoCommand>
#include <QTableWidget>
#include <QToolBar>
#include <QVBoxLayout>
#include <memory>
#include <nostalgia/core/consts.hpp>
#include <nostalgia/common/point.hpp>
#include <qnamespace.h>
#include "consts.hpp"
#include "util.hpp"
#include "tilesheeteditor.hpp"
namespace nostalgia::core {
class ModAfterDialog: public QDialog {
private:
QComboBox *const m_afterColor = new QComboBox(this);
QSpinBox *const m_mod = new QSpinBox(this);
public:
ModAfterDialog(const QStringList &palette, QWidget *parent = nullptr): QDialog(parent) {
auto range = palette.size();
auto okBtn = new QPushButton(tr("&OK"), this);
setWindowTitle(tr("Mod Colors After"));
setWindowModality(Qt::WindowModality::ApplicationModal);
for (int i = 0; i < palette.size(); ++i) {
const auto c = palette[i];
m_afterColor->addItem(QString("%1: %2").arg(i + 1).arg(c));
}
m_mod->setMinimum(-range + 1);
m_mod->setMaximum(range - 1);
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);
}
int color() {
return m_afterColor->currentIndex() - 1;
}
int mod() {
return m_mod->value();
}
};
struct LabeledSpinner: public QWidget {
QSpinBox *const spinBox = new QSpinBox(this);
LabeledSpinner(QString name, int minimum, int value) {
auto lyt = new QHBoxLayout;
setLayout(lyt);
auto lbl = new QLabel(name, this);
lbl->setBuddy(spinBox);
lyt->addWidget(lbl);
lyt->addWidget(spinBox);
spinBox->setMinimum(minimum);
spinBox->setValue(value);
}
virtual ~LabeledSpinner() = default;
};
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<int>(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 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) {
}
virtual ~ModPixelsCommand() = default;
void addPixel(int index, int mod) {
m_pixelUpdates[index] = mod;
}
int id() const override {
return static_cast<int>(CommandId::ModPixel);
}
void redo() override {
m_sheetData->modPixels(m_pixelUpdates, 1);
}
void undo() override {
m_sheetData->modPixels(m_pixelUpdates, -1);
}
};
class UpdatePixelsCommand: public QUndoCommand {
private:
struct PixelUpdate {
QPointer<QQuickItem> item;
int oldColorId = 0;
bool operator==(const PixelUpdate &o) const {
return item == o.item;
}
bool operator<(const PixelUpdate &o) const {
return item < o.item;
}
operator quint64() const {
return reinterpret_cast<quint64>(item.data());
}
};
uint64_t m_cmdIdx = 0;
int m_newColorId = 0;
const QStringList &m_palette;
QVector<int> &m_pixels;
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) {
m_newColorId = newColorId;
m_cmdIdx = cmdIdx;
PixelUpdate pu;
pu.item = pixelItem;
pu.oldColorId = m_palette.indexOf(pixelItem->property("color").value<QColor>().name(QColor::HexArgb));
m_pixelUpdates.insert(pu);
setObsolete(pu.oldColorId == newColorId);
}
virtual ~UpdatePixelsCommand() = default;
int id() const override {
return static_cast<int>(CommandId::UpdatePixel);
}
bool mergeWith(const QUndoCommand *cmd) override {
auto other = dynamic_cast<const UpdatePixelsCommand*>(cmd);
if (other && m_cmdIdx == other->m_cmdIdx) {
m_pixelUpdates.unite(other->m_pixelUpdates);
return true;
}
return false;
}
void redo() override {
for (const auto &pu : m_pixelUpdates) {
pu.item->setProperty("color", m_palette[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;
}
}
void undo() override {
for (const auto &pu : m_pixelUpdates) {
pu.item->setProperty("color", m_palette[pu.oldColorId]);
m_pixels[pu.item->property("pixelNumber").toInt()] = pu.oldColorId;
}
}
};
class InsertTileCommand: public QUndoCommand {
private:
SheetData *m_sheetData = nullptr;
int m_idx = 0;
bool m_delete = false;
QVector<int> m_tileRestore;
public:
InsertTileCommand(SheetData *sheetData, int idx, bool del = false) {
m_sheetData = sheetData;
m_idx = idx;
m_delete = del;
}
virtual ~InsertTileCommand() = default;
int id() const override {
return static_cast<int>(CommandId::InsertTile);
}
void redo() override {
if (m_delete) {
m_tileRestore = m_sheetData->deleteTile(m_idx);
} else {
m_sheetData->insertTile(m_idx, m_tileRestore);
}
}
void undo() override {
if (m_delete) {
m_sheetData->insertTile(m_idx, m_tileRestore);
} else {
m_tileRestore = m_sheetData->deleteTile(m_idx);
}
}
};
void TileSheetEditorColorTableDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opt, const QModelIndex &idx) const {
if (idx.column() != 1) {
QStyledItemDelegate::paint(painter, opt, idx);
} else {
auto color = idx.model()->data(idx, Qt::DisplayRole).toString();
painter->fillRect(opt.rect, QColor(color));
}
}
SheetData::SheetData(QUndoStack *undoStack): m_cmdStack(undoStack) {
}
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;
emit changeOccurred();
}
}
void SheetData::beginCmd() {
++m_cmdIdx;
}
void SheetData::endCmd() {
m_prevPixelUpdated = nullptr;
}
void SheetData::insertTileCmd(int tileIdx) {
m_cmdStack->push(new InsertTileCommand(this, tileIdx));
}
void SheetData::deleteTileCmd(int tileIdx) {
m_cmdStack->push(new InsertTileCommand(this, tileIdx, true));
}
int SheetData::columns() const {
return m_columns;
}
int SheetData::rows() const {
return m_rows;
}
const QVector<int> &SheetData::pixels() const {
return m_pixels;
}
QStringList SheetData::palette() const {
return m_palette;
}
QString SheetData::palettePath() const {
return m_currentPalettePath;
}
void SheetData::load(const studio::Context *ctx, QString ngPath, QString palPath) {
auto ng = ctx->project->loadObj<NostalgiaGraphic>(ngPath);
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::reload(const studio::Context *ctx) {
load(ctx, m_tilesheetPath, m_currentPalettePath);
}
void SheetData::save(const studio::Context *ctx, QString ngPath) const {
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);
}
emit paletteChanged();
}
void SheetData::setPalette(const studio::Context *ctx, QString palPath) {
std::unique_ptr<NostalgiaPalette> npal;
try {
npal = ctx->project->loadObj<NostalgiaPalette>(palPath);
} catch (ox::Error) {
qWarning() << "Could not open palette" << palPath;
}
if (npal) {
setPalette(npal.get());
}
m_currentPalettePath = palPath;
emit changeOccurred();
}
void SheetData::insertTile(int tileIdx, QVector<int> tileData) {
auto pxIdx = tileIdx * PixelsPerTile;
m_pixels.insert(pxIdx, PixelsPerTile, 0);
std::copy(tileData.begin(), tileData.end(), &m_pixels[pxIdx]);
emit pixelsChanged();
emit changeOccurred();
}
QVector<int> SheetData::deleteTile(int tileIdx) {
QVector<int> out;
auto pxIdx = tileIdx * PixelsPerTile;
std::copy(m_pixels.begin() + pxIdx,
m_pixels.begin() + (pxIdx + PixelsPerTile),
std::back_inserter(out));
m_pixels.remove(pxIdx, PixelsPerTile);
emit pixelsChanged();
emit changeOccurred();
return out;
}
void SheetData::setSelectedColor(int index) {
m_selectedColor = index;
}
void SheetData::modGteCmd(int color, int mod) {
auto cmd = new ModPixelsCommand(this, mod);
for (int i = 0; i < m_pixels.size(); ++i) {
const auto p = m_pixels[i];
if (p >= color) {
const auto mx = (m_palette.size() - p) - 1;
cmd->addPixel(i, std::clamp(mod, -p, mx));
}
}
m_cmdStack->push(cmd);
}
void SheetData::modPixels(const QHash<int, int> &mods, int inversion) {
for (const auto index : mods.keys()) {
const auto mod = mods[index];
m_pixels[index] += mod * inversion;
}
emit pixelsChanged();
emit changeOccurred();
}
std::unique_ptr<NostalgiaGraphic> SheetData::toNostalgiaGraphic() const {
auto ng = std::make_unique<NostalgiaGraphic>();
const auto highestColorIdx = static_cast<uint8_t>(*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;
auto pixelCount = static_cast<std::size_t>(ng->rows * ng->columns * PixelsPerTile);
if (ng->bpp == 4) {
ng->tiles.resize(pixelCount / 2);
for (std::size_t i = 0; i < pixelCount && i < static_cast<std::size_t>(m_pixels.size()); ++i) {
if (i & 1) {
ng->tiles[i / 2] |= static_cast<uint8_t>(m_pixels[i]) << 4;
} else {
ng->tiles[i / 2] = static_cast<uint8_t>(m_pixels[i]);
}
}
} else {
ng->tiles.resize(pixelCount);
for (std::size_t i = 0; i < ng->tiles.size(); ++i) {
ng->tiles[i] = static_cast<uint8_t>(m_pixels[i]);
}
}
return ng;
}
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::updateColumns(int columns) {
m_cmdStack->push(new UpdateDimensionsCommand(this, UpdateDimensionsCommand::Dimension::Columns, m_columns, columns));
}
void SheetData::updateRows(int rows) {
m_cmdStack->push(new UpdateDimensionsCommand(this, UpdateDimensionsCommand::Dimension::Rows, m_rows, rows));
}
void SheetData::updatePixels(const NostalgiaGraphic *ng) {
m_pixels.clear();
if (ng->bpp == 8) {
for (std::size_t i = 0; i < ng->tiles.size(); i++) {
m_pixels.push_back(ng->tiles[i]);
}
} else {
for (std::size_t i = 0; i < ng->tiles.size() * 2; i++) {
uint8_t p;
if (i & 1) {
p = ng->tiles[i / 2] >> 4;
} else {
p = ng->tiles[i / 2] & 0xF;
}
m_pixels.push_back(p);
}
}
emit pixelsChanged();
}
TileSheetEditor::TileSheetEditor(QString path, const studio::Context *ctx, QWidget *parent): studio::Editor(parent), m_sheetData(undoStack()) {
m_ctx = ctx;
m_itemPath = path;
m_itemName = path.mid(path.lastIndexOf('/'));
auto lyt = new QVBoxLayout(this);
m_splitter = new QSplitter(this);
auto canvasParent = new QWidget(m_splitter);
auto canvasLyt = new QVBoxLayout(canvasParent);
m_canvas = new QQuickWidget(canvasParent);
canvasLyt->addWidget(m_canvas);
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);
tb->addWidget(m_tilesX);
tb->addWidget(m_tilesY);
tb->addWidget(m_updateAfterBtn);
canvasLyt->setMenuBar(tb);
lyt->addWidget(m_splitter);
m_splitter->addWidget(canvasParent);
m_splitter->addWidget(setupColorPicker(m_splitter));
m_splitter->setStretchFactor(0, 1);
connect(&m_sheetData, &SheetData::columnsChanged, [this](int val) {
disconnect(m_tilesX->spinBox, QOverload<int>::of(&QSpinBox::valueChanged), &m_sheetData, &SheetData::updateColumns);
m_tilesX->spinBox->setValue(val);
connect(m_tilesX->spinBox, QOverload<int>::of(&QSpinBox::valueChanged), &m_sheetData, &SheetData::updateColumns);
});
connect(&m_sheetData, &SheetData::rowsChanged, [this](int val) {
disconnect(m_tilesY->spinBox, QOverload<int>::of(&QSpinBox::valueChanged), &m_sheetData, &SheetData::updateRows);
m_tilesY->spinBox->setValue(val);
connect(m_tilesY->spinBox, QOverload<int>::of(&QSpinBox::valueChanged), &m_sheetData, &SheetData::updateRows);
});
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);
m_canvas->rootContext()->setContextProperty("sheetData", &m_sheetData);
m_canvas->setSource(QUrl::fromLocalFile(":/qml/TileSheetEditor.qml"));
m_canvas->setResizeMode(QQuickWidget::SizeRootObjectToView);
setPalette();
setColorTable();
restoreState();
connect(&m_sheetData, &SheetData::changeOccurred, [this] { setUnsavedChanges(true); });
connect(&m_sheetData, &SheetData::paletteChanged, this, &TileSheetEditor::setColorTable);
setExportable(true);
}
TileSheetEditor::~TileSheetEditor() {
saveState();
}
QString TileSheetEditor::itemName() const {
return m_itemName;
}
void TileSheetEditor::exportFile() {
auto path = QFileDialog::getSaveFileName(this, tr("Export to Image"), "",
tr("PNG (*.png)"));
if (path != "") {
auto ng = m_sheetData.toNostalgiaGraphic();
QString palPath;
if (palPath == "" && ng->defaultPalette.type() == ox::FileAddressType::Path) {
palPath = ng->defaultPalette.getPath().value;
}
std::unique_ptr<NostalgiaPalette> npal;
try {
npal = m_ctx->project->loadObj<NostalgiaPalette>(palPath);
} catch (ox::Error) {
qWarning() << "Could not open palette" << palPath;
// TODO: message box to notify of failure
}
toQImage(ng.get(), npal.get()).save(path, "PNG");
}
}
void TileSheetEditor::saveItem() {
m_sheetData.save(m_ctx, m_itemPath);
}
QWidget *TileSheetEditor::setupColorPicker(QWidget *parent) {
auto colorPicker = new QWidget(parent);
auto lyt = new QVBoxLayout(colorPicker);
m_colorPicker.palette = new QComboBox(colorPicker);
m_colorPicker.colorTable = new QTableWidget(colorPicker);
m_colorPicker.colorTable->setItemDelegate(&m_colorTableDelegate);
m_colorPicker.colorTable->setColumnCount(2);
m_colorPicker.colorTable->setSelectionBehavior(QAbstractItemView::SelectRows);
m_colorPicker.colorTable->setSelectionMode(QAbstractItemView::SingleSelection);
m_colorPicker.colorTable->setHorizontalHeaderLabels(QStringList() << tr("Hex Code") << tr("Color"));
m_colorPicker.colorTable->horizontalHeader()->setStretchLastSection(true);
connect(m_colorPicker.colorTable, &QTableWidget::itemSelectionChanged, this, &TileSheetEditor::colorSelected);
connect(m_colorPicker.palette, &QComboBox::currentTextChanged, this, [this](QString name) {
m_sheetData.setPalette(m_ctx, palettePath(name));
});
lyt->addWidget(m_colorPicker.palette);
lyt->addWidget(m_colorPicker.colorTable);
m_ctx->project->subscribe(studio::ProjectEvent::FileRecognized, m_colorPicker.palette, [this](QString path) {
if (path.startsWith(PaletteDir) && path.endsWith(FileExt_npal)) {
auto name = paletteName(path);
m_colorPicker.palette->addItem(name);
}
});
m_ctx->project->subscribe(studio::ProjectEvent::FileUpdated, m_colorPicker.palette, [this](QString path) {
if (path == m_sheetData.palettePath()) {
m_sheetData.reload(m_ctx);
}
});
m_ctx->project->subscribe(studio::ProjectEvent::FileDeleted, m_colorPicker.palette, [this](QString path) {
if (path.startsWith(PaletteDir) && path.endsWith(FileExt_npal)) {
auto name = paletteName(path);
auto idx = m_colorPicker.palette->findText(name);
m_colorPicker.palette->removeItem(idx);
}
});
return colorPicker;
}
void TileSheetEditor::setPalette() {
auto name = paletteName(m_sheetData.palettePath());
m_colorPicker.palette->setCurrentText(name);
}
void TileSheetEditor::saveState() {
QSettings settings(m_ctx->orgName, PluginName);
settings.beginGroup("TileSheetEditor/" + m_itemName);
settings.setValue("m_splitter/state", m_splitter->saveState());
settings.setValue("m_sheetData/tileRows", m_sheetData.rows());
settings.setValue("m_sheetData/tileColumns", m_sheetData.columns());
settings.setValue("m_colorPicker.colorTable/geometry", m_colorPicker.colorTable->horizontalHeader()->saveState());
settings.endGroup();
}
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_colorPicker.colorTable->horizontalHeader()->restoreState(settings.value("m_colorPicker.colorTable/geometry", m_colorPicker.colorTable->horizontalHeader()->saveState()).toByteArray());
settings.endGroup();
}
QString TileSheetEditor::paletteName(QString palettePath) const {
return studio::filePathToName(palettePath, PaletteDir, FileExt_npal);
}
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;
QImage dst(w, h, QImage::Format_RGB32);
auto setPixel = [&dst, npal, ng](std::size_t i, uint8_t p) {
const auto color = toColor32(npal->colors[p]) >> 8;
const auto pt = idxToPt(i, ng->columns);
dst.setPixel(pt.x, pt.y, color);
};
if (ng->bpp == 4) {
for (std::size_t i = 0; i < ng->tiles.size(); ++i) {
auto p1 = ng->tiles[i] & 0xF;
auto p2 = ng->tiles[i] >> 4;
setPixel(i * 2 + 0, p1);
setPixel(i * 2 + 1, p2);
}
} else {
for (std::size_t i = 0; i < ng->tiles.size(); i++) {
const auto p = ng->tiles[i];
setPixel(i, p);
}
}
return dst;
}
void TileSheetEditor::colorSelected() {
m_sheetData.setSelectedColor(m_colorPicker.colorTable->currentRow());
}
void TileSheetEditor::setColorTable() {
auto hexColors = m_sheetData.palette();
auto tbl = m_colorPicker.colorTable;
tbl->setRowCount(hexColors.size());
for (int i = 0; i < hexColors.size(); i++) {
auto hexCode = new QTableWidgetItem;
hexCode->setText(hexColors[i]);
hexCode->setFont(QFont("monospace"));
tbl->setItem(i, 0, hexCode);
auto hi = tbl->item(i, 0);
hi->setFlags(hi->flags() & ~Qt::ItemIsEditable);
auto color = new QTableWidgetItem;
color->setText(hexColors[i]);
tbl->setItem(i, 1, color);
auto ci = tbl->item(i, 1);
ci->setFlags(ci->flags() & ~Qt::ItemIsEditable);
}
}
void TileSheetEditor::updateAfterClicked() {
auto d = new ModAfterDialog(m_sheetData.palette(), this);
connect(d, &ModAfterDialog::accepted, [this, d] {
m_sheetData.modGteCmd(d->color(), d->mod());
});
connect(d, &ModAfterDialog::finished, d, &ModAfterDialog::deleteLater);
d->open();
}
}