[nostalgia/core] Add Fill command to tilesheet editor and make ClipboardObject more efficient

This commit is contained in:
Gary Talent 2022-03-11 20:41:36 -06:00
parent ae80d22769
commit 7df978605f
7 changed files with 391 additions and 337 deletions

View File

@ -5,6 +5,7 @@
#pragma once #pragma once
#include <ox/claw/claw.hpp> #include <ox/claw/claw.hpp>
#include <ox/std/memory.hpp>
#include <ox/std/string.hpp> #include <ox/std/string.hpp>
#include "context.hpp" #include "context.hpp"
@ -15,32 +16,21 @@ ox::String getClipboardText(class Context *ctx) noexcept;
void setClipboardText(class Context *ctx, const ox::String &text) noexcept; void setClipboardText(class Context *ctx, const ox::String &text) noexcept;
void setClipboardObject([[maybe_unused]] class Context *ctx, [[maybe_unused]] auto *obj) noexcept { template<typename T>
void setClipboardObject([[maybe_unused]] class Context *ctx, [[maybe_unused]] ox::UniquePtr<T> obj) noexcept {
#ifndef OX_BARE_METAL #ifndef OX_BARE_METAL
auto [buff, err] = ox::writeClaw(obj); ctx->clipboard = std::move(obj);
if (err) {
oxLogError(err);
return;
}
ctx->clipboard = std::move(buff);
#endif
}
ox::Error getClipboardObject([[maybe_unused]] class Context *ctx, [[maybe_unused]] auto *obj) noexcept {
#ifndef OX_BARE_METAL
return ox::readClaw<decltype(*obj)>(ctx->clipboard).moveTo(obj);
#else
return OxError(0);
#endif #endif
} }
template<typename T> template<typename T>
ox::Result<T> getClipboardObject([[maybe_unused]] class Context *ctx) noexcept { ox::Result<T*> getClipboardObject([[maybe_unused]] class Context *ctx) noexcept {
#ifndef OX_BARE_METAL #ifndef OX_BARE_METAL
return ox::readClaw<T>(ctx->clipboard); if (ctx->clipboard && ctx->clipboard->typeMatch(T::TypeName, T::TypeVersion)) {
#else return static_cast<T*>(ctx->clipboard.get());
return OxError(0); }
#endif #endif
return OxError(1);
} }
} }

View File

@ -17,6 +17,25 @@ class Size;
namespace nostalgia::core { namespace nostalgia::core {
class BaseClipboardObject {
public:
virtual ~BaseClipboardObject() = default;
virtual ox::String typeId() const noexcept = 0;
constexpr auto typeMatch(auto name, auto version) const noexcept {
auto inId = ox::sfmt("{};{}", name, version);
return typeId() == inId;
}
};
template<typename T>
class ClipboardObject: public BaseClipboardObject {
ox::String typeId() const noexcept final {
return ox::sfmt("{};{}", T::TypeName, T::TypeVersion);
}
};
class Context; class Context;
class Drawer; class Drawer;
@ -59,7 +78,7 @@ class Context {
#ifndef OX_BARE_METAL #ifndef OX_BARE_METAL
AssetManager assetManager; AssetManager assetManager;
int uninterruptedRefreshes = 0; int uninterruptedRefreshes = 0;
ox::Buffer clipboard; ox::UniquePtr<BaseClipboardObject> clipboard;
#endif #endif
protected: protected:
#ifndef OX_BARE_METAL #ifndef OX_BARE_METAL

View File

@ -56,7 +56,7 @@ void TileSheetEditorImGui::draw(core::Context*) noexcept {
{ {
ImGui::BeginChild("ToolBox", ImVec2(m_palViewWidth - 24, 30), true); ImGui::BeginChild("ToolBox", ImVec2(m_palViewWidth - 24, 30), true);
{ {
const auto btnSz = ImVec2(40, 14); const auto btnSz = ImVec2(45, 14);
if (ImGui::Selectable("Draw", m_tool == Tool::Draw, 0, btnSz)) { if (ImGui::Selectable("Draw", m_tool == Tool::Draw, 0, btnSz)) {
m_tool = Tool::Draw; m_tool = Tool::Draw;
model()->clearSelection(); model()->clearSelection();
@ -65,6 +65,11 @@ void TileSheetEditorImGui::draw(core::Context*) noexcept {
if (ImGui::Selectable("Select", m_tool == Tool::Select, 0, btnSz)) { if (ImGui::Selectable("Select", m_tool == Tool::Select, 0, btnSz)) {
m_tool = Tool::Select; m_tool = Tool::Select;
} }
ImGui::SameLine();
if (ImGui::Selectable("Fill", m_tool == Tool::Fill, 0, btnSz)) {
m_tool = Tool::Fill;
model()->clearSelection();
}
} }
ImGui::EndChild(); ImGui::EndChild();
const auto ySize = paneSize.y - 36; const auto ySize = paneSize.y - 36;
@ -212,10 +217,13 @@ void TileSheetEditorImGui::drawTileSheet(const geo::Vec2 &fbSize) noexcept {
m_tileSheetEditor.clickDraw(fbSize, clickPos); m_tileSheetEditor.clickDraw(fbSize, clickPos);
break; break;
case Tool::Fill: case Tool::Fill:
case Tool::None: m_tileSheetEditor.clickFill(fbSize, clickPos);
break;
case Tool::Select: case Tool::Select:
m_tileSheetEditor.clickSelect(fbSize, clickPos); m_tileSheetEditor.clickSelect(fbSize, clickPos);
break; break;
case Tool::None:
break;
} }
} }
} }

View File

@ -8,19 +8,320 @@
#include <nostalgia/core/clipboard.hpp> #include <nostalgia/core/clipboard.hpp>
#include <nostalgia/core/media.hpp> #include <nostalgia/core/media.hpp>
#include <ox/std/buffer.hpp> #include <ox/std/buffer.hpp>
#include <ox/std/memory.hpp>
#include "tilesheeteditormodel.hpp" #include "tilesheeteditormodel.hpp"
namespace nostalgia::core { namespace nostalgia::core {
void TileSheetClipboard::addPixel(const geo::Point &pt, uint16_t colorIdx) noexcept { class TileSheetClipboard: public ClipboardObject<TileSheetClipboard> {
m_pixels.emplace_back(colorIdx, pt); public:
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.studio.TileSheetClipboard";
static constexpr auto TypeVersion = 1;
oxModelFriend(TileSheetClipboard);
struct Pixel {
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.studio.TileSheetClipboard.Pixel";
static constexpr auto TypeVersion = 1;
uint16_t colorIdx = 0;
geo::Point pt;
};
protected:
ox::Vector<Pixel> m_pixels;
public:
constexpr void addPixel(const geo::Point &pt, uint16_t colorIdx) noexcept {
m_pixels.emplace_back(colorIdx, pt);
}
constexpr const ox::Vector<Pixel> &pixels() const noexcept {
return m_pixels;
}
};
oxModelBegin(TileSheetClipboard::Pixel)
oxModelField(colorIdx)
oxModelField(pt)
oxModelEnd()
oxModelBegin(TileSheetClipboard)
oxModelFieldRename(pixels, m_pixels)
oxModelEnd()
// Command IDs to use with QUndoCommand::id()
enum class CommandId {
Draw = 1,
AddSubSheet = 2,
RmSubSheet = 3,
UpdateSubSheet = 4,
Paste = 5,
};
constexpr bool operator==(CommandId c, int i) noexcept {
return static_cast<int>(c) == i;
} }
void TileSheetClipboard::clear() noexcept { constexpr bool operator==(int i, CommandId c) noexcept {
m_pixels.clear(); return static_cast<int>(c) == i;
} }
class DrawCommand: public studio::UndoCommand {
private:
struct Change {
uint32_t idx = 0;
uint16_t oldPalIdx = 0;
};
TileSheet *m_img = nullptr;
TileSheet::SubSheetIdx m_subSheetIdx;
ox::Vector<Change, 8> m_changes;
int m_palIdx = 0;
public:
constexpr DrawCommand(TileSheet *img, const TileSheet::SubSheetIdx &subSheetIdx, std::size_t idx, int palIdx) noexcept {
m_img = img;
auto &subsheet = m_img->getSubSheet(subSheetIdx);
m_subSheetIdx = subSheetIdx;
m_changes.emplace_back(idx, subsheet.getPixel(m_img->bpp, idx));
m_palIdx = palIdx;
}
constexpr DrawCommand(TileSheet *img, const TileSheet::SubSheetIdx &subSheetIdx, ox::Vector<std::size_t> idxList, int palIdx) noexcept {
m_img = img;
auto &subsheet = m_img->getSubSheet(subSheetIdx);
m_subSheetIdx = subSheetIdx;
for (const auto idx : idxList) {
m_changes.emplace_back(idx, subsheet.getPixel(m_img->bpp, idx));
}
m_palIdx = palIdx;
}
constexpr auto append(std::size_t idx) noexcept {
auto &subsheet = m_img->getSubSheet(m_subSheetIdx);
if (m_changes.back().value.idx != idx && subsheet.getPixel(m_img->bpp, idx) != m_palIdx) {
// duplicate entries are bad
auto existing = std::find_if(m_changes.cbegin(), m_changes.cend(), [idx](const auto &c) {
return c.idx == idx;
});
if (existing == m_changes.cend()) {
m_changes.emplace_back(idx, subsheet.getPixel(m_img->bpp, idx));
subsheet.setPixel(m_img->bpp, idx, m_palIdx);
return true;
}
}
return false;
}
constexpr auto append(ox::Vector<std::size_t> idxList) noexcept {
auto out = false;
for (auto idx : idxList) {
out = append(idx) || out;
}
return out;
}
void redo() noexcept final {
auto &subsheet = m_img->getSubSheet(m_subSheetIdx);
for (const auto &c : m_changes) {
subsheet.setPixel(m_img->bpp, c.idx, m_palIdx);
}
}
void undo() noexcept final {
auto &subsheet = m_img->getSubSheet(m_subSheetIdx);
for (const auto &c : m_changes) {
subsheet.setPixel(m_img->bpp, c.idx, c.oldPalIdx);
}
}
[[nodiscard]]
int commandId() const noexcept final {
return static_cast<int>(CommandId::Draw);
}
};
class PasteCommand: public studio::UndoCommand {
private:
struct Change {
uint32_t idx = 0;
uint16_t newPalIdx = 0;
uint16_t oldPalIdx = 0;
};
TileSheet *m_img = nullptr;
TileSheet::SubSheetIdx m_subSheetIdx;
ox::Vector<Change> m_changes;
public:
constexpr PasteCommand(TileSheet *img, const TileSheet::SubSheetIdx &subSheetIdx, const geo::Point &dstStart, const geo::Point &dstEnd, const TileSheetClipboard &cb) noexcept {
m_img = img;
m_subSheetIdx = subSheetIdx;
const auto &subsheet = m_img->getSubSheet(subSheetIdx);
for (const auto &p : cb.pixels()) {
const auto dstPt = p.pt + dstStart;
if (dstPt.x <= dstEnd.x && dstPt.y <= dstEnd.y) {
const auto idx = subsheet.idx(dstPt);
m_changes.emplace_back(idx, p.colorIdx, subsheet.getPixel(m_img->bpp, idx));
}
}
}
void redo() noexcept final {
auto &subsheet = m_img->getSubSheet(m_subSheetIdx);
for (const auto &c : m_changes) {
subsheet.setPixel(m_img->bpp, c.idx, c.newPalIdx);
}
}
void undo() noexcept final {
auto &subsheet = m_img->getSubSheet(m_subSheetIdx);
for (const auto &c : m_changes) {
subsheet.setPixel(m_img->bpp, c.idx, c.oldPalIdx);
}
}
[[nodiscard]]
int commandId() const noexcept final {
return static_cast<int>(CommandId::Paste);
}
};
class AddSubSheetCommand: public studio::UndoCommand {
private:
TileSheet *m_img = nullptr;
TileSheet::SubSheetIdx m_parentIdx;
ox::Vector<TileSheet::SubSheetIdx, 2> m_addedSheets;
public:
constexpr AddSubSheetCommand(TileSheet *img, const TileSheet::SubSheetIdx &parentIdx) noexcept {
m_img = img;
m_parentIdx = parentIdx;
auto &parent = m_img->getSubSheet(m_parentIdx);
if (parent.subsheets.size()) {
auto idx = m_parentIdx;
idx.emplace_back(parent.subsheets.size());
m_addedSheets.push_back(idx);
} else {
auto idx = m_parentIdx;
idx.emplace_back(0);
m_addedSheets.push_back(idx);
idx.back().value = 1;
m_addedSheets.push_back(idx);
}
}
void redo() noexcept final {
auto &parent = m_img->getSubSheet(m_parentIdx);
if (m_addedSheets.size() < 2) {
auto i = parent.subsheets.size();
parent.subsheets.emplace_back(ox::sfmt("Subsheet {}", i).c_str(), 1, 1);
} else {
parent.subsheets.emplace_back("Subsheet 0", parent.columns, parent.rows, std::move(parent.pixels));
parent.rows = 0;
parent.columns = 0;
parent.subsheets.emplace_back("Subsheet 1", 1, 1);
}
}
void undo() noexcept final {
auto &parent = m_img->getSubSheet(m_parentIdx);
if (parent.subsheets.size() == 2) {
auto s = parent.subsheets[0];
parent.rows = s.rows;
parent.columns = s.columns;
parent.pixels = std::move(s.pixels);
parent.subsheets.clear();
} else {
for (auto idx = m_addedSheets.rbegin(); idx != m_addedSheets.rend(); ++idx) {
oxLogError(m_img->rmSubSheet(*idx));
}
}
}
[[nodiscard]]
int commandId() const noexcept final {
return static_cast<int>(CommandId::AddSubSheet);
}
};
class RmSubSheetCommand: public studio::UndoCommand {
private:
TileSheet *m_img = nullptr;
TileSheet::SubSheetIdx m_idx;
TileSheet::SubSheetIdx m_parentIdx;
TileSheet::SubSheet m_sheet;
public:
constexpr RmSubSheetCommand(TileSheet *img, const TileSheet::SubSheetIdx &idx) noexcept {
m_img = img;
m_idx = idx;
m_parentIdx = idx;
m_parentIdx.pop_back();
auto &parent = m_img->getSubSheet(m_parentIdx);
m_sheet = parent.subsheets[idx.back().value];
}
void redo() noexcept final {
auto &parent = m_img->getSubSheet(m_parentIdx);
oxLogError(parent.subsheets.erase(m_idx.back().value).error);
}
void undo() noexcept final {
auto &parent = m_img->getSubSheet(m_parentIdx);
auto i = m_idx.back().value;
parent.subsheets.insert(i, m_sheet);
}
[[nodiscard]]
int commandId() const noexcept final {
return static_cast<int>(CommandId::RmSubSheet);
}
};
class UpdateSubSheetCommand: public studio::UndoCommand {
private:
TileSheet *m_img = nullptr;
TileSheet::SubSheetIdx m_idx;
TileSheet::SubSheet m_sheet;
ox::String m_newName;
int m_newCols = 0;
int m_newRows = 0;
public:
constexpr UpdateSubSheetCommand(TileSheet *img, const TileSheet::SubSheetIdx &idx,
const ox::String &name, int cols, int rows) noexcept {
m_img = img;
m_idx = idx;
m_sheet = img->getSubSheet(idx);
m_newName = name;
m_newCols = cols;
m_newRows = rows;
}
void redo() noexcept final {
auto &sheet = m_img->getSubSheet(m_idx);
sheet.name = m_newName;
sheet.columns = m_newCols;
sheet.rows = m_newRows;
oxLogError(sheet.setPixelCount(m_img->bpp, static_cast<std::size_t>(PixelsPerTile * m_newCols * m_newRows)));
}
void undo() noexcept final {
auto &sheet = m_img->getSubSheet(m_idx);
sheet = m_sheet;
}
[[nodiscard]]
int commandId() const noexcept final {
return static_cast<int>(CommandId::UpdateSubSheet);
}
};
TileSheetEditorModel::TileSheetEditorModel(Context *ctx, const ox::String &path) { TileSheetEditorModel::TileSheetEditorModel(Context *ctx, const ox::String &path) {
m_ctx = ctx; m_ctx = ctx;
@ -32,7 +333,7 @@ TileSheetEditorModel::TileSheetEditorModel(Context *ctx, const ox::String &path)
} }
void TileSheetEditorModel::cut() { void TileSheetEditorModel::cut() {
TileSheetClipboard cb; auto cb = ox::make_unique<TileSheetClipboard>();;
for (int y = m_selectionBounds.y; y <= m_selectionBounds.y2(); ++y) { for (int y = m_selectionBounds.y; y <= m_selectionBounds.y2(); ++y) {
for (int x = m_selectionBounds.x; x <= m_selectionBounds.x2(); ++x) { for (int x = m_selectionBounds.x; x <= m_selectionBounds.x2(); ++x) {
auto pt = geo::Point(x, y); auto pt = geo::Point(x, y);
@ -42,14 +343,14 @@ void TileSheetEditorModel::cut() {
s->setPixel(m_img.bpp, idx, 0); s->setPixel(m_img.bpp, idx, 0);
pt.x -= m_selectionBounds.x; pt.x -= m_selectionBounds.x;
pt.y -= m_selectionBounds.y; pt.y -= m_selectionBounds.y;
cb.addPixel(pt, c); cb->addPixel(pt, c);
} }
} }
setClipboardObject(m_ctx, &cb); setClipboardObject(m_ctx, std::move(cb));
} }
void TileSheetEditorModel::copy() { void TileSheetEditorModel::copy() {
TileSheetClipboard cb; auto cb = ox::make_unique<TileSheetClipboard>();;
for (int y = m_selectionBounds.y; y <= m_selectionBounds.y2(); ++y) { for (int y = m_selectionBounds.y; y <= m_selectionBounds.y2(); ++y) {
for (int x = m_selectionBounds.x; x <= m_selectionBounds.x2(); ++x) { for (int x = m_selectionBounds.x; x <= m_selectionBounds.x2(); ++x) {
auto pt = geo::Point(x, y); auto pt = geo::Point(x, y);
@ -58,10 +359,10 @@ void TileSheetEditorModel::copy() {
const auto c = s->getPixel(m_img.bpp, idx); const auto c = s->getPixel(m_img.bpp, idx);
pt.x -= m_selectionBounds.x; pt.x -= m_selectionBounds.x;
pt.y -= m_selectionBounds.y; pt.y -= m_selectionBounds.y;
cb.addPixel(pt, c); cb->addPixel(pt, c);
} }
} }
setClipboardObject(m_ctx, &cb); setClipboardObject(m_ctx, std::move(cb));
} }
void TileSheetEditorModel::paste() { void TileSheetEditorModel::paste() {
@ -74,12 +375,12 @@ void TileSheetEditorModel::paste() {
const auto s = activeSubSheet(); const auto s = activeSubSheet();
const auto pt1 = m_selectionOrigin == geo::Point(-1, -1) ? geo::Point(0, 0) : m_selectionOrigin; const auto pt1 = m_selectionOrigin == geo::Point(-1, -1) ? geo::Point(0, 0) : m_selectionOrigin;
const auto pt2 = geo::Point(s->columns * TileWidth, s->rows * TileHeight); const auto pt2 = geo::Point(s->columns * TileWidth, s->rows * TileHeight);
pushCommand(new PasteCommand(&m_img, m_activeSubsSheetIdx, pt1, pt2, cb)); pushCommand(new PasteCommand(&m_img, m_activeSubsSheetIdx, pt1, pt2, *cb));
} }
void TileSheetEditorModel::drawCommand(const geo::Point &pt, std::size_t palIdx) noexcept { void TileSheetEditorModel::drawCommand(const geo::Point &pt, std::size_t palIdx) noexcept {
const auto &activeSubSheet = m_img.getSubSheet(m_activeSubsSheetIdx); const auto &activeSubSheet = m_img.getSubSheet(m_activeSubsSheetIdx);
const auto idx = ptToIdx(pt, activeSubSheet.columns); const auto idx = activeSubSheet.idx(pt);
if (idx >= activeSubSheet.pixelCnt(m_img.bpp)) { if (idx >= activeSubSheet.pixelCnt(m_img.bpp)) {
return; return;
} }
@ -112,6 +413,31 @@ void TileSheetEditorModel::setActiveSubsheet(const TileSheet::SubSheetIdx &idx)
this->activeSubsheetChanged.emit(m_activeSubsSheetIdx); this->activeSubsheetChanged.emit(m_activeSubsSheetIdx);
} }
void TileSheetEditorModel::fill(const geo::Point &pt, int palIdx) noexcept {
const auto s = m_img.getSubSheet(m_activeSubsSheetIdx);
// build idx list
ox::Array<bool, PixelsPerTile> updateMap = {};
const auto oldColor = s.getPixel(m_img.bpp, pt);
if (oldColor == palIdx) {
return;
}
getFillPixels(updateMap.data(), pt, oldColor);
ox::Vector<std::size_t> idxList;
auto i = s.idx(pt) / PixelsPerTile * PixelsPerTile;
for (auto u : updateMap) {
if (u) {
idxList.emplace_back(i);
}
++i;
}
// do updates to sheet
if (m_ongoingDrawCommand) {
m_updated = m_updated || m_ongoingDrawCommand->append(idxList);
} else if (s.getPixel(m_img.bpp, pt) != palIdx) {
pushCommand(new DrawCommand(&m_img, m_activeSubsSheetIdx, idxList, palIdx));
}
}
void TileSheetEditorModel::select(const geo::Point &pt) noexcept { void TileSheetEditorModel::select(const geo::Point &pt) noexcept {
if (!m_selectionOngoing) { if (!m_selectionOngoing) {
m_selectionOrigin = pt; m_selectionOrigin = pt;
@ -193,16 +519,16 @@ void TileSheetEditorModel::getFillPixels(bool *pixels, const geo::Point &pt, int
const auto tile = tileIdx(pt); const auto tile = tileIdx(pt);
// mark pixels to update // mark pixels to update
pixels[idx % PixelsPerTile] = true; pixels[idx % PixelsPerTile] = true;
if (!pixels[leftIdx % PixelsPerTile] && tile == tileIdx(leftPt) && activeSubSheet.pixels[leftIdx] == oldColor) { if (!pixels[leftIdx % PixelsPerTile] && tile == tileIdx(leftPt) && activeSubSheet.getPixel(m_img.bpp, leftIdx) == oldColor) {
getFillPixels(pixels, leftPt, oldColor); getFillPixels(pixels, leftPt, oldColor);
} }
if (!pixels[rightIdx % PixelsPerTile] && tile == tileIdx(rightPt) && activeSubSheet.pixels[rightIdx] == oldColor) { if (!pixels[rightIdx % PixelsPerTile] && tile == tileIdx(rightPt) && activeSubSheet.getPixel(m_img.bpp, rightIdx) == oldColor) {
getFillPixels(pixels, rightPt, oldColor); getFillPixels(pixels, rightPt, oldColor);
} }
if (!pixels[topIdx % PixelsPerTile] && tile == tileIdx(topPt) && activeSubSheet.pixels[topIdx] == oldColor) { if (!pixels[topIdx % PixelsPerTile] && tile == tileIdx(topPt) && activeSubSheet.getPixel(m_img.bpp, topIdx) == oldColor) {
getFillPixels(pixels, topPt, oldColor); getFillPixels(pixels, topPt, oldColor);
} }
if (!pixels[bottomIdx % PixelsPerTile] && tile == tileIdx(bottomPt) && activeSubSheet.pixels[bottomIdx] == oldColor) { if (!pixels[bottomIdx % PixelsPerTile] && tile == tileIdx(bottomPt) && activeSubSheet.getPixel(m_img.bpp, bottomIdx) == oldColor) {
getFillPixels(pixels, bottomPt, oldColor); getFillPixels(pixels, bottomPt, oldColor);
} }
} }

View File

@ -15,301 +15,6 @@
namespace nostalgia::core { namespace nostalgia::core {
class TileSheetClipboard {
public:
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.studio.TileSheetClipboard";
static constexpr auto TypeVersion = 1;
oxModelFriend(TileSheetClipboard);
struct Pixel {
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.studio.TileSheetClipboard.Pixel";
static constexpr auto TypeVersion = 1;
uint16_t colorIdx = 0;
geo::Point pt;
};
protected:
ox::Vector<Pixel> m_pixels;
public:
void addPixel(const geo::Point &pt, uint16_t colorIdx) noexcept;
[[nodiscard]]
bool empty() const;
void pastePixels(const geo::Point &pt, ox::Vector<int> *tgt, int tgtColumns) const;
void clear() noexcept;
constexpr const ox::Vector<Pixel> &pixels() const noexcept {
return m_pixels;
}
};
oxModelBegin(TileSheetClipboard::Pixel)
oxModelField(colorIdx)
oxModelField(pt)
oxModelEnd()
oxModelBegin(TileSheetClipboard)
oxModelFieldRename(pixels, m_pixels)
oxModelEnd()
// Command IDs to use with QUndoCommand::id()
enum class CommandId {
Draw = 1,
AddSubSheet = 2,
RmSubSheet = 3,
UpdateSubSheet = 4,
Paste = 5,
};
constexpr bool operator==(CommandId c, int i) noexcept {
return static_cast<int>(c) == i;
}
constexpr bool operator==(int i, CommandId c) noexcept {
return static_cast<int>(c) == i;
}
struct DrawCommand: public studio::UndoCommand {
private:
struct Change {
uint32_t idx = 0;
uint16_t oldPalIdx = 0;
};
TileSheet *m_img = nullptr;
TileSheet::SubSheetIdx m_subSheetIdx;
ox::Vector<Change, 8> m_changes;
int m_palIdx = 0;
public:
constexpr DrawCommand(TileSheet *img, const TileSheet::SubSheetIdx &subSheetIdx, std::size_t idx, int palIdx) noexcept {
m_img = img;
auto &subsheet = m_img->getSubSheet(subSheetIdx);
m_subSheetIdx = subSheetIdx;
m_changes.emplace_back(idx, subsheet.getPixel(m_img->bpp, idx));
m_palIdx = palIdx;
}
constexpr auto append(std::size_t idx) noexcept {
auto &subsheet = m_img->getSubSheet(m_subSheetIdx);
if (m_changes.back().value.idx != idx && subsheet.getPixel(m_img->bpp, idx) != m_palIdx) {
// duplicate entries are bad
auto existing = std::find_if(m_changes.cbegin(), m_changes.cend(), [idx](const auto &c) {
return c.idx == idx;
});
if (existing == m_changes.cend()) {
m_changes.emplace_back(idx, subsheet.getPixel(m_img->bpp, idx));
subsheet.setPixel(m_img->bpp, idx, m_palIdx);
return true;
}
}
return false;
}
void redo() noexcept final {
auto &subsheet = m_img->getSubSheet(m_subSheetIdx);
for (const auto &c : m_changes) {
subsheet.setPixel(m_img->bpp, c.idx, m_palIdx);
}
}
void undo() noexcept final {
auto &subsheet = m_img->getSubSheet(m_subSheetIdx);
for (const auto &c : m_changes) {
subsheet.setPixel(m_img->bpp, c.idx, c.oldPalIdx);
}
}
[[nodiscard]]
int commandId() const noexcept final {
return static_cast<int>(CommandId::Draw);
}
};
struct PasteCommand: public studio::UndoCommand {
private:
struct Change {
uint32_t idx = 0;
uint16_t newPalIdx = 0;
uint16_t oldPalIdx = 0;
};
TileSheet *m_img = nullptr;
TileSheet::SubSheetIdx m_subSheetIdx;
ox::Vector<Change> m_changes;
public:
constexpr PasteCommand(TileSheet *img, const TileSheet::SubSheetIdx &subSheetIdx, const geo::Point &dstStart, const geo::Point &dstEnd, const TileSheetClipboard &cb) noexcept {
m_img = img;
m_subSheetIdx = subSheetIdx;
const auto &subsheet = m_img->getSubSheet(subSheetIdx);
for (const auto &p : cb.pixels()) {
const auto dstPt = p.pt + dstStart;
if (dstPt.x <= dstEnd.x && dstPt.y <= dstEnd.y) {
const auto idx = subsheet.idx(dstPt);
m_changes.emplace_back(idx, p.colorIdx, subsheet.getPixel(m_img->bpp, idx));
}
}
}
void redo() noexcept final {
auto &subsheet = m_img->getSubSheet(m_subSheetIdx);
for (const auto &c : m_changes) {
subsheet.setPixel(m_img->bpp, c.idx, c.newPalIdx);
}
}
void undo() noexcept final {
auto &subsheet = m_img->getSubSheet(m_subSheetIdx);
for (const auto &c : m_changes) {
subsheet.setPixel(m_img->bpp, c.idx, c.oldPalIdx);
}
}
[[nodiscard]]
int commandId() const noexcept final {
return static_cast<int>(CommandId::Paste);
}
};
struct AddSubSheetCommand: public studio::UndoCommand {
private:
TileSheet *m_img = nullptr;
TileSheet::SubSheetIdx m_parentIdx;
ox::Vector<TileSheet::SubSheetIdx, 2> m_addedSheets;
public:
constexpr AddSubSheetCommand(TileSheet *img, const TileSheet::SubSheetIdx &parentIdx) noexcept {
m_img = img;
m_parentIdx = parentIdx;
auto &parent = m_img->getSubSheet(m_parentIdx);
if (parent.subsheets.size()) {
auto idx = m_parentIdx;
idx.emplace_back(parent.subsheets.size());
m_addedSheets.push_back(idx);
} else {
auto idx = m_parentIdx;
idx.emplace_back(0);
m_addedSheets.push_back(idx);
idx.back().value = 1;
m_addedSheets.push_back(idx);
}
}
void redo() noexcept final {
auto &parent = m_img->getSubSheet(m_parentIdx);
if (m_addedSheets.size() < 2) {
auto i = parent.subsheets.size();
parent.subsheets.emplace_back(ox::sfmt("Subsheet {}", i).c_str(), 1, 1);
} else {
parent.subsheets.emplace_back("Subsheet 0", parent.columns, parent.rows, std::move(parent.pixels));
parent.rows = 0;
parent.columns = 0;
parent.subsheets.emplace_back("Subsheet 1", 1, 1);
}
}
void undo() noexcept final {
auto &parent = m_img->getSubSheet(m_parentIdx);
if (parent.subsheets.size() == 2) {
auto s = parent.subsheets[0];
parent.rows = s.rows;
parent.columns = s.columns;
parent.pixels = std::move(s.pixels);
parent.subsheets.clear();
} else {
for (auto idx = m_addedSheets.rbegin(); idx != m_addedSheets.rend(); ++idx) {
oxLogError(m_img->rmSubSheet(*idx));
}
}
}
[[nodiscard]]
int commandId() const noexcept final {
return static_cast<int>(CommandId::AddSubSheet);
}
};
struct RmSubSheetCommand: public studio::UndoCommand {
private:
TileSheet *m_img = nullptr;
TileSheet::SubSheetIdx m_idx;
TileSheet::SubSheetIdx m_parentIdx;
TileSheet::SubSheet m_sheet;
public:
constexpr RmSubSheetCommand(TileSheet *img, const TileSheet::SubSheetIdx &idx) noexcept {
m_img = img;
m_idx = idx;
m_parentIdx = idx;
m_parentIdx.pop_back();
auto &parent = m_img->getSubSheet(m_parentIdx);
m_sheet = parent.subsheets[idx.back().value];
}
void redo() noexcept final {
auto &parent = m_img->getSubSheet(m_parentIdx);
oxLogError(parent.subsheets.erase(m_idx.back().value).error);
}
void undo() noexcept final {
auto &parent = m_img->getSubSheet(m_parentIdx);
auto i = m_idx.back().value;
parent.subsheets.insert(i, m_sheet);
}
[[nodiscard]]
int commandId() const noexcept final {
return static_cast<int>(CommandId::RmSubSheet);
}
};
struct UpdateSubSheetCommand: public studio::UndoCommand {
private:
TileSheet *m_img = nullptr;
TileSheet::SubSheetIdx m_idx;
TileSheet::SubSheet m_sheet;
ox::String m_newName;
int m_newCols = 0;
int m_newRows = 0;
public:
constexpr UpdateSubSheetCommand(TileSheet *img, const TileSheet::SubSheetIdx &idx,
const ox::String &name, int cols, int rows) noexcept {
m_img = img;
m_idx = idx;
m_sheet = img->getSubSheet(idx);
m_newName = name;
m_newCols = cols;
m_newRows = rows;
}
void redo() noexcept final {
auto &sheet = m_img->getSubSheet(m_idx);
sheet.name = m_newName;
sheet.columns = m_newCols;
sheet.rows = m_newRows;
oxLogError(sheet.setPixelCount(m_img->bpp, static_cast<std::size_t>(PixelsPerTile * m_newCols * m_newRows)));
}
void undo() noexcept final {
auto &sheet = m_img->getSubSheet(m_idx);
sheet = m_sheet;
}
[[nodiscard]]
int commandId() const noexcept final {
return static_cast<int>(CommandId::UpdateSubSheet);
}
};
class TileSheetEditorModel: public ox::SignalHandler { class TileSheetEditorModel: public ox::SignalHandler {
public: public:
@ -320,7 +25,7 @@ class TileSheetEditorModel: public ox::SignalHandler {
TileSheet::SubSheetIdx m_activeSubsSheetIdx; TileSheet::SubSheetIdx m_activeSubsSheetIdx;
AssetRef<Palette> m_pal; AssetRef<Palette> m_pal;
studio::UndoStack m_undoStack; studio::UndoStack m_undoStack;
DrawCommand *m_ongoingDrawCommand = nullptr; class DrawCommand *m_ongoingDrawCommand = nullptr;
bool m_updated = false; bool m_updated = false;
Context *m_ctx = nullptr; Context *m_ctx = nullptr;
ox::String m_path; ox::String m_path;
@ -374,6 +79,8 @@ class TileSheetEditorModel: public ox::SignalHandler {
return m_activeSubsSheetIdx; return m_activeSubsSheetIdx;
} }
void fill(const geo::Point &pt, int palIdx) noexcept;
void select(const geo::Point &pt) noexcept; void select(const geo::Point &pt) noexcept;
void completeSelection() noexcept; void completeSelection() noexcept;
@ -394,8 +101,6 @@ class TileSheetEditorModel: public ox::SignalHandler {
bool pixelSelected(std::size_t idx) const noexcept; bool pixelSelected(std::size_t idx) const noexcept;
protected: protected:
void saveItem();
void getFillPixels(bool *pixels, const geo::Point &pt, int oldColor) const noexcept; void getFillPixels(bool *pixels, const geo::Point &pt, int oldColor) const noexcept;
private: private:

View File

@ -53,7 +53,6 @@ void TileSheetEditorView::scrollH(const geo::Vec2 &paneSz, float wheelh) noexcep
void TileSheetEditorView::clickDraw(const geo::Vec2 &paneSize, const geo::Vec2 &clickPos) noexcept { void TileSheetEditorView::clickDraw(const geo::Vec2 &paneSize, const geo::Vec2 &clickPos) noexcept {
const auto pt = clickPoint(paneSize, clickPos); const auto pt = clickPoint(paneSize, clickPos);
m_model.drawCommand(pt, m_palIdx); m_model.drawCommand(pt, m_palIdx);
m_model.clearSelection();
} }
void TileSheetEditorView::clickSelect(const geo::Vec2 &paneSize, const geo::Vec2 &clickPos) noexcept { void TileSheetEditorView::clickSelect(const geo::Vec2 &paneSize, const geo::Vec2 &clickPos) noexcept {
@ -61,6 +60,11 @@ void TileSheetEditorView::clickSelect(const geo::Vec2 &paneSize, const geo::Vec2
m_model.select(pt); m_model.select(pt);
} }
void TileSheetEditorView::clickFill(const geo::Vec2 &paneSize, const geo::Vec2 &clickPos) noexcept {
const auto pt = clickPoint(paneSize, clickPos);
m_model.fill(pt, m_palIdx);
}
void TileSheetEditorView::releaseMouseButton() noexcept { void TileSheetEditorView::releaseMouseButton() noexcept {
m_model.endDrawCommand(); m_model.endDrawCommand();
m_model.completeSelection(); m_model.completeSelection();

View File

@ -59,6 +59,8 @@ class TileSheetEditorView: public ox::SignalHandler {
void clickSelect(const geo::Vec2 &paneSize, const geo::Vec2 &clickPos) noexcept; void clickSelect(const geo::Vec2 &paneSize, const geo::Vec2 &clickPos) noexcept;
void clickFill(const geo::Vec2 &paneSize, const geo::Vec2 &clickPos) noexcept;
void releaseMouseButton() noexcept; void releaseMouseButton() noexcept;
void scrollV(const geo::Vec2 &paneSz, float wheel, bool zoomMod) noexcept; void scrollV(const geo::Vec2 &paneSz, float wheel, bool zoomMod) noexcept;