[nostalgia/core/studio] Add copy/cut/paste support to TileSheet Editor

This commit is contained in:
2022-03-10 20:42:21 -06:00
parent a6983ce53b
commit 415c2574bb
3 changed files with 154 additions and 65 deletions
@@ -15,12 +15,54 @@
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 {
@@ -88,6 +130,53 @@ struct DrawCommand: public studio::UndoCommand {
};
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 &dst, 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 idx = subsheet.idx(p.pt + dst);
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) {
if (c.idx < subsheet.pixelCnt(m_img->bpp)) {
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) {
if (c.idx < subsheet.pixelCnt(m_img->bpp)) {
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;
@@ -207,12 +296,7 @@ struct UpdateSubSheetCommand: public studio::UndoCommand {
sheet.name = m_newName;
sheet.columns = m_newCols;
sheet.rows = m_newRows;
oxDebugf("old cols: {}, old rows: {}", sheet.columns, sheet.rows);
oxDebugf("new cols: {}, new rows: {}", m_newCols, m_newRows);
oxDebugf("bpp: {}", m_img->bpp);
oxDebugf("pixel count before: {}", sheet.pixels.size());
oxLogError(sheet.setPixelCount(m_img->bpp, static_cast<std::size_t>(PixelsPerTile * m_newCols * m_newRows)));
oxDebugf("pixel count after: {}", sheet.pixels.size());
}
void undo() noexcept final {
@@ -227,45 +311,6 @@ struct UpdateSubSheetCommand: public studio::UndoCommand {
};
struct TileSheetClipboard {
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.studio.TileSheetClipboard";
static constexpr auto TypeVersion = 1;
oxModelFriend(TileSheetClipboard);
protected:
ox::Vector<int> m_pixels;
geo::Point m_p1;
geo::Point m_p2;
public:
void addPixel(int color);
[[nodiscard]]
bool empty() const;
void pastePixels(const geo::Point &pt, ox::Vector<int> *tgt, int tgtColumns) const;
void setPoints(const geo::Point &p1, const geo::Point &p2);
[[nodiscard]]
constexpr geo::Point point1() const noexcept {
return m_p1;
}
[[nodiscard]]
constexpr geo::Point point2() const noexcept {
return m_p2;
}
};
oxModelBegin(TileSheetClipboard)
oxModelFieldRename(pixels, m_pixels)
oxModelFieldRename(p1, m_p1)
oxModelFieldRename(p2, m_p2)
oxModelEnd()
class TileSheetEditorModel: public ox::SignalHandler {
public:
@@ -280,11 +325,9 @@ class TileSheetEditorModel: public ox::SignalHandler {
bool m_updated = false;
Context *m_ctx = nullptr;
ox::String m_path;
ox::Vector<std::size_t> m_selectedPixels; // pixel idx values
bool m_selectionOngoing = false;
geo::Point m_selectionPt1 = {-1, -1};
geo::Point m_selectionPt2 = {-1, -1};
geo::Bounds m_selectionBounds;
geo::Point m_selectionOrigin = {-1, -1};
geo::Bounds m_selectionBounds = {{-1, -1}, {-1, -1}};
public:
TileSheetEditorModel(Context *ctx, const ox::String &path);