[nostalgia/core/studio] Restructure core studio for maintainability
This commit is contained in:
parent
d5a1e11ec5
commit
7418a7126a
@ -1,17 +1,9 @@
|
|||||||
add_library(
|
add_library(NostalgiaCore-Studio)
|
||||||
NostalgiaCore-Studio
|
|
||||||
paletteeditor.cpp
|
|
||||||
tilesheeteditorview.cpp
|
|
||||||
tilesheeteditormodel.cpp
|
|
||||||
tilesheetpixelgrid.cpp
|
|
||||||
tilesheetpixels.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
add_library(
|
add_library(
|
||||||
NostalgiaCore-Studio-ImGui
|
NostalgiaCore-Studio-ImGui
|
||||||
studiomodule.cpp
|
studiomodule.cpp
|
||||||
paletteeditor-imgui.cpp
|
tilesheeteditor/tilesheeteditor-imgui.cpp
|
||||||
tilesheeteditor-imgui.cpp
|
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(
|
target_link_libraries(
|
||||||
@ -24,7 +16,6 @@ target_link_libraries(
|
|||||||
NostalgiaCore-Studio-ImGui PUBLIC
|
NostalgiaCore-Studio-ImGui PUBLIC
|
||||||
NostalgiaCore-Studio
|
NostalgiaCore-Studio
|
||||||
Studio
|
Studio
|
||||||
lodepng
|
|
||||||
)
|
)
|
||||||
|
|
||||||
install(
|
install(
|
||||||
@ -34,3 +25,6 @@ install(
|
|||||||
LIBRARY DESTINATION
|
LIBRARY DESTINATION
|
||||||
${NOSTALGIA_DIST_MODULE}
|
${NOSTALGIA_DIST_MODULE}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
add_subdirectory(paletteeditor)
|
||||||
|
add_subdirectory(tilesheeteditor)
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
target_sources(
|
||||||
|
NostalgiaCore-Studio PRIVATE
|
||||||
|
paletteeditor.cpp
|
||||||
|
paletteeditor-imgui.cpp
|
||||||
|
)
|
@ -6,8 +6,8 @@
|
|||||||
|
|
||||||
#include <studio/studio.hpp>
|
#include <studio/studio.hpp>
|
||||||
|
|
||||||
#include "paletteeditor-imgui.hpp"
|
#include "paletteeditor/paletteeditor-imgui.hpp"
|
||||||
#include "tilesheeteditor-imgui.hpp"
|
#include "tilesheeteditor/tilesheeteditor-imgui.hpp"
|
||||||
|
|
||||||
namespace nostalgia::core {
|
namespace nostalgia::core {
|
||||||
|
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
target_sources(
|
||||||
|
NostalgiaCore-Studio PRIVATE
|
||||||
|
tilesheeteditorview.cpp
|
||||||
|
tilesheeteditormodel.cpp
|
||||||
|
tilesheetpixelgrid.cpp
|
||||||
|
tilesheetpixels.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(
|
||||||
|
NostalgiaCore-Studio-ImGui PUBLIC
|
||||||
|
lodepng
|
||||||
|
)
|
||||||
|
|
||||||
|
add_subdirectory(commands)
|
@ -0,0 +1,11 @@
|
|||||||
|
target_sources(
|
||||||
|
NostalgiaCore-Studio PRIVATE
|
||||||
|
addsubsheetcommand.cpp
|
||||||
|
cutpastecommand.cpp
|
||||||
|
deletetilescommand.cpp
|
||||||
|
drawcommand.cpp
|
||||||
|
inserttilescommand.cpp
|
||||||
|
palettechangecommand.cpp
|
||||||
|
rmsubsheetcommand.cpp
|
||||||
|
updatesubsheetcommand.cpp
|
||||||
|
)
|
@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "addsubsheetcommand.hpp"
|
||||||
|
|
||||||
|
namespace nostalgia::core {
|
||||||
|
|
||||||
|
AddSubSheetCommand::AddSubSheetCommand(
|
||||||
|
TileSheet &img,
|
||||||
|
TileSheet::SubSheetIdx parentIdx) noexcept:
|
||||||
|
m_img(img), m_parentIdx(std::move(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(0u);
|
||||||
|
m_addedSheets.push_back(idx);
|
||||||
|
*idx.back().value = 1;
|
||||||
|
m_addedSheets.push_back(idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddSubSheetCommand::redo() noexcept {
|
||||||
|
auto &parent = m_img.getSubSheet(m_parentIdx);
|
||||||
|
if (m_addedSheets.size() < 2) {
|
||||||
|
auto i = parent.subsheets.size();
|
||||||
|
parent.subsheets.emplace_back(m_img.idIt++, ox::sfmt("Subsheet {}", i), 1, 1, m_img.bpp);
|
||||||
|
} else {
|
||||||
|
parent.subsheets.emplace_back(m_img.idIt++, "Subsheet 0", parent.columns, parent.rows, std::move(parent.pixels));
|
||||||
|
parent.rows = 0;
|
||||||
|
parent.columns = 0;
|
||||||
|
parent.subsheets.emplace_back(m_img.idIt++, "Subsheet 1", 1, 1, m_img.bpp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddSubSheetCommand::undo() noexcept {
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int AddSubSheetCommand::commandId() const noexcept {
|
||||||
|
return static_cast<int>(CommandId::AddSubSheet);
|
||||||
|
}
|
||||||
|
|
||||||
|
TileSheet::SubSheetIdx const &AddSubSheetCommand::subsheetIdx() const noexcept {
|
||||||
|
return m_parentIdx;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "commands.hpp"
|
||||||
|
|
||||||
|
namespace nostalgia::core {
|
||||||
|
|
||||||
|
class AddSubSheetCommand: public TileSheetCommand {
|
||||||
|
private:
|
||||||
|
TileSheet &m_img;
|
||||||
|
TileSheet::SubSheetIdx m_parentIdx;
|
||||||
|
ox::Vector<TileSheet::SubSheetIdx, 2> m_addedSheets;
|
||||||
|
|
||||||
|
public:
|
||||||
|
AddSubSheetCommand(TileSheet &img, TileSheet::SubSheetIdx parentIdx) noexcept;
|
||||||
|
|
||||||
|
void redo() noexcept final;
|
||||||
|
|
||||||
|
void undo() noexcept final;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
int commandId() const noexcept final;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
TileSheet::SubSheetIdx const&subsheetIdx() const noexcept override;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <studio/undostack.hpp>
|
||||||
|
#include <nostalgia/core/tilesheet.hpp>
|
||||||
|
|
||||||
|
namespace nostalgia::core {
|
||||||
|
|
||||||
|
// Command IDs to use with QUndoCommand::id()
|
||||||
|
enum class CommandId {
|
||||||
|
Draw = 1,
|
||||||
|
AddSubSheet = 2,
|
||||||
|
RmSubSheet = 3,
|
||||||
|
DeleteTile = 4,
|
||||||
|
InsertTile = 4,
|
||||||
|
UpdateSubSheet = 5,
|
||||||
|
Cut = 6,
|
||||||
|
Paste = 7,
|
||||||
|
PaletteChange = 8,
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
class TileSheetCommand: public studio::UndoCommand {
|
||||||
|
public:
|
||||||
|
[[nodiscard]]
|
||||||
|
virtual TileSheet::SubSheetIdx const&subsheetIdx() const noexcept = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "cutpastecommand.hpp"
|
||||||
|
|
||||||
|
namespace nostalgia::core {
|
||||||
|
|
||||||
|
TileSheetClipboard::Pixel::Pixel(uint16_t pColorIdx, ox::Point pPt) noexcept {
|
||||||
|
colorIdx = pColorIdx;
|
||||||
|
pt = pPt;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void TileSheetClipboard::addPixel(const ox::Point &pt, uint16_t colorIdx) noexcept {
|
||||||
|
m_pixels.emplace_back(colorIdx, pt);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ox::Vector<TileSheetClipboard::Pixel> &TileSheetClipboard::pixels() const noexcept {
|
||||||
|
return m_pixels;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
CutPasteCommand::CutPasteCommand(
|
||||||
|
CommandId commandId,
|
||||||
|
TileSheet &img,
|
||||||
|
TileSheet::SubSheetIdx subSheetIdx,
|
||||||
|
const ox::Point &dstStart,
|
||||||
|
const ox::Point &dstEnd,
|
||||||
|
const TileSheetClipboard &cb) noexcept:
|
||||||
|
m_commandId(commandId),
|
||||||
|
m_img(img),
|
||||||
|
m_subSheetIdx(std::move(subSheetIdx)) {
|
||||||
|
const auto &subsheet = m_img.getSubSheet(m_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(static_cast<uint32_t>(idx), p.colorIdx, subsheet.getPixel(m_img.bpp, idx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CutPasteCommand::redo() noexcept {
|
||||||
|
auto &subsheet = m_img.getSubSheet(m_subSheetIdx);
|
||||||
|
for (const auto &c : m_changes) {
|
||||||
|
subsheet.setPixel(m_img.bpp, c.idx, static_cast<uint8_t>(c.newPalIdx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CutPasteCommand::undo() noexcept {
|
||||||
|
auto &subsheet = m_img.getSubSheet(m_subSheetIdx);
|
||||||
|
for (const auto &c : m_changes) {
|
||||||
|
subsheet.setPixel(m_img.bpp, c.idx, static_cast<uint8_t>(c.oldPalIdx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int CutPasteCommand::commandId() const noexcept {
|
||||||
|
return static_cast<int>(m_commandId);
|
||||||
|
}
|
||||||
|
|
||||||
|
TileSheet::SubSheetIdx const &CutPasteCommand::subsheetIdx() const noexcept {
|
||||||
|
return m_subSheetIdx;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <turbine/clipboard.hpp>
|
||||||
|
|
||||||
|
#include "commands.hpp"
|
||||||
|
|
||||||
|
namespace nostalgia::core {
|
||||||
|
|
||||||
|
class TileSheetClipboard: public turbine::ClipboardObject<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;
|
||||||
|
ox::Point pt;
|
||||||
|
Pixel(uint16_t pColorIdx, ox::Point pPt) noexcept;
|
||||||
|
};
|
||||||
|
protected:
|
||||||
|
ox::Vector<Pixel> m_pixels;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void addPixel(ox::Point const&pt, uint16_t colorIdx) noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
ox::Vector<Pixel> const&pixels() const noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
oxModelBegin(TileSheetClipboard::Pixel)
|
||||||
|
oxModelField(colorIdx)
|
||||||
|
oxModelField(pt)
|
||||||
|
oxModelEnd()
|
||||||
|
|
||||||
|
oxModelBegin(TileSheetClipboard)
|
||||||
|
oxModelFieldRename(pixels, m_pixels)
|
||||||
|
oxModelEnd()
|
||||||
|
|
||||||
|
class CutPasteCommand: public TileSheetCommand {
|
||||||
|
private:
|
||||||
|
struct Change {
|
||||||
|
uint32_t idx = 0;
|
||||||
|
uint16_t newPalIdx = 0;
|
||||||
|
uint16_t oldPalIdx = 0;
|
||||||
|
constexpr Change(uint32_t pIdx, uint16_t pNewPalIdx, uint16_t pOldPalIdx) noexcept {
|
||||||
|
idx = pIdx;
|
||||||
|
newPalIdx = pNewPalIdx;
|
||||||
|
oldPalIdx = pOldPalIdx;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
CommandId m_commandId;
|
||||||
|
TileSheet &m_img;
|
||||||
|
TileSheet::SubSheetIdx m_subSheetIdx;
|
||||||
|
ox::Vector<Change> m_changes;
|
||||||
|
|
||||||
|
public:
|
||||||
|
CutPasteCommand(
|
||||||
|
CommandId commandId,
|
||||||
|
TileSheet &img,
|
||||||
|
TileSheet::SubSheetIdx subSheetIdx,
|
||||||
|
ox::Point const&dstStart,
|
||||||
|
ox::Point const&dstEnd,
|
||||||
|
TileSheetClipboard const&cb) noexcept;
|
||||||
|
|
||||||
|
void redo() noexcept final;
|
||||||
|
|
||||||
|
void undo() noexcept final;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
int commandId() const noexcept final;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
TileSheet::SubSheetIdx const&subsheetIdx() const noexcept override;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "deletetilescommand.hpp"
|
||||||
|
|
||||||
|
namespace nostalgia::core {
|
||||||
|
|
||||||
|
core::DeleteTilesCommand::DeleteTilesCommand(
|
||||||
|
TileSheet &img,
|
||||||
|
TileSheet::SubSheetIdx idx,
|
||||||
|
std::size_t tileIdx,
|
||||||
|
std::size_t tileCnt) noexcept:
|
||||||
|
m_img(img),
|
||||||
|
m_idx(std::move(idx)) {
|
||||||
|
const unsigned bytesPerTile = m_img.bpp == 4 ? PixelsPerTile / 2 : PixelsPerTile;
|
||||||
|
m_deletePos = tileIdx * bytesPerTile;
|
||||||
|
m_deleteSz = tileCnt * bytesPerTile;
|
||||||
|
m_deletedPixels.resize(m_deleteSz);
|
||||||
|
// copy pixels to be erased
|
||||||
|
{
|
||||||
|
auto &s = m_img.getSubSheet(m_idx);
|
||||||
|
auto &p = s.pixels;
|
||||||
|
auto dst = m_deletedPixels.data();
|
||||||
|
auto src = p.data() + m_deletePos;
|
||||||
|
const auto sz = m_deleteSz * sizeof(decltype(p[0]));
|
||||||
|
ox_memcpy(dst, src, sz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void core::DeleteTilesCommand::redo() noexcept {
|
||||||
|
auto &s = m_img.getSubSheet(m_idx);
|
||||||
|
auto &p = s.pixels;
|
||||||
|
auto srcPos = m_deletePos + m_deleteSz;
|
||||||
|
const auto src = p.data() + srcPos;
|
||||||
|
const auto dst1 = p.data() + m_deletePos;
|
||||||
|
const auto dst2 = p.data() + (p.size() - m_deleteSz);
|
||||||
|
ox_memmove(dst1, src, p.size() - srcPos);
|
||||||
|
ox_memset(dst2, 0, m_deleteSz * sizeof(decltype(p[0])));
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeleteTilesCommand::undo() noexcept {
|
||||||
|
auto &s = m_img.getSubSheet(m_idx);
|
||||||
|
auto &p = s.pixels;
|
||||||
|
const auto src = p.data() + m_deletePos;
|
||||||
|
const auto dst1 = p.data() + m_deletePos + m_deleteSz;
|
||||||
|
const auto dst2 = src;
|
||||||
|
const auto sz = p.size() - m_deletePos - m_deleteSz;
|
||||||
|
ox_memmove(dst1, src, sz);
|
||||||
|
ox_memcpy(dst2, m_deletedPixels.data(), m_deletedPixels.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
int DeleteTilesCommand::commandId() const noexcept {
|
||||||
|
return static_cast<int>(CommandId::DeleteTile);
|
||||||
|
}
|
||||||
|
|
||||||
|
TileSheet::SubSheetIdx const &DeleteTilesCommand::subsheetIdx() const noexcept {
|
||||||
|
return m_idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "commands.hpp"
|
||||||
|
|
||||||
|
namespace nostalgia::core {
|
||||||
|
|
||||||
|
class DeleteTilesCommand: public TileSheetCommand {
|
||||||
|
private:
|
||||||
|
TileSheet &m_img;
|
||||||
|
TileSheet::SubSheetIdx m_idx;
|
||||||
|
std::size_t m_deletePos = 0;
|
||||||
|
std::size_t m_deleteSz = 0;
|
||||||
|
ox::Vector<uint8_t> m_deletedPixels = {};
|
||||||
|
|
||||||
|
public:
|
||||||
|
DeleteTilesCommand(
|
||||||
|
TileSheet &img,
|
||||||
|
TileSheet::SubSheetIdx idx,
|
||||||
|
std::size_t tileIdx,
|
||||||
|
std::size_t tileCnt) noexcept;
|
||||||
|
|
||||||
|
void redo() noexcept final;
|
||||||
|
|
||||||
|
void undo() noexcept final;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
int commandId() const noexcept final;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
TileSheet::SubSheetIdx const&subsheetIdx() const noexcept override;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "drawcommand.hpp"
|
||||||
|
|
||||||
|
namespace nostalgia::core {
|
||||||
|
|
||||||
|
DrawCommand::DrawCommand(
|
||||||
|
TileSheet &img,
|
||||||
|
TileSheet::SubSheetIdx subSheetIdx,
|
||||||
|
std::size_t idx,
|
||||||
|
int palIdx) noexcept:
|
||||||
|
m_img(img),
|
||||||
|
m_subSheetIdx(std::move(subSheetIdx)) {
|
||||||
|
auto &subsheet = m_img.getSubSheet(m_subSheetIdx);
|
||||||
|
m_changes.emplace_back(static_cast<uint32_t>(idx), subsheet.getPixel(m_img.bpp, idx));
|
||||||
|
m_palIdx = palIdx;
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawCommand::DrawCommand(
|
||||||
|
TileSheet &img,
|
||||||
|
TileSheet::SubSheetIdx subSheetIdx,
|
||||||
|
const ox::Vector<std::size_t> &idxList,
|
||||||
|
int palIdx) noexcept:
|
||||||
|
m_img(img),
|
||||||
|
m_subSheetIdx(std::move(subSheetIdx)) {
|
||||||
|
auto &subsheet = m_img.getSubSheet(m_subSheetIdx);
|
||||||
|
for (const auto idx : idxList) {
|
||||||
|
m_changes.emplace_back(static_cast<uint32_t>(idx), subsheet.getPixel(m_img.bpp, idx));
|
||||||
|
}
|
||||||
|
m_palIdx = palIdx;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DrawCommand::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 = ox::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(static_cast<uint32_t>(idx), subsheet.getPixel(m_img.bpp, idx));
|
||||||
|
subsheet.setPixel(m_img.bpp, idx, static_cast<uint8_t>(m_palIdx));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DrawCommand::append(const ox::Vector<std::size_t> &idxList) noexcept {
|
||||||
|
auto out = false;
|
||||||
|
for (auto idx : idxList) {
|
||||||
|
out = append(idx) || out;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawCommand::redo() noexcept {
|
||||||
|
auto &subsheet = m_img.getSubSheet(m_subSheetIdx);
|
||||||
|
for (const auto &c : m_changes) {
|
||||||
|
subsheet.setPixel(m_img.bpp, c.idx, static_cast<uint8_t>(m_palIdx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawCommand::undo() noexcept {
|
||||||
|
auto &subsheet = m_img.getSubSheet(m_subSheetIdx);
|
||||||
|
for (const auto &c : m_changes) {
|
||||||
|
subsheet.setPixel(m_img.bpp, c.idx, static_cast<uint8_t>(c.oldPalIdx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int DrawCommand::commandId() const noexcept {
|
||||||
|
return static_cast<int>(CommandId::Draw);
|
||||||
|
}
|
||||||
|
|
||||||
|
TileSheet::SubSheetIdx const &DrawCommand::subsheetIdx() const noexcept {
|
||||||
|
return m_subSheetIdx;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "commands.hpp"
|
||||||
|
|
||||||
|
namespace nostalgia::core {
|
||||||
|
|
||||||
|
class DrawCommand: public TileSheetCommand {
|
||||||
|
private:
|
||||||
|
struct Change {
|
||||||
|
uint32_t idx = 0;
|
||||||
|
uint16_t oldPalIdx = 0;
|
||||||
|
constexpr Change(uint32_t pIdx, uint16_t pOldPalIdx) noexcept {
|
||||||
|
idx = pIdx;
|
||||||
|
oldPalIdx = pOldPalIdx;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
TileSheet &m_img;
|
||||||
|
TileSheet::SubSheetIdx m_subSheetIdx;
|
||||||
|
ox::Vector<Change, 8> m_changes;
|
||||||
|
int m_palIdx = 0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
DrawCommand(
|
||||||
|
TileSheet &img,
|
||||||
|
TileSheet::SubSheetIdx subSheetIdx,
|
||||||
|
std::size_t idx,
|
||||||
|
int palIdx) noexcept;
|
||||||
|
|
||||||
|
DrawCommand(
|
||||||
|
TileSheet &img,
|
||||||
|
TileSheet::SubSheetIdx subSheetIdx,
|
||||||
|
const ox::Vector<std::size_t> &idxList,
|
||||||
|
int palIdx) noexcept;
|
||||||
|
|
||||||
|
bool append(std::size_t idx) noexcept;
|
||||||
|
|
||||||
|
bool append(const ox::Vector<std::size_t> &idxList) noexcept;
|
||||||
|
|
||||||
|
void redo() noexcept final;
|
||||||
|
|
||||||
|
void undo() noexcept final;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
int commandId() const noexcept final;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
TileSheet::SubSheetIdx const&subsheetIdx() const noexcept override;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "inserttilescommand.hpp"
|
||||||
|
|
||||||
|
namespace nostalgia::core {
|
||||||
|
|
||||||
|
core::InsertTilesCommand::InsertTilesCommand(
|
||||||
|
TileSheet &img,
|
||||||
|
TileSheet::SubSheetIdx idx,
|
||||||
|
std::size_t tileIdx,
|
||||||
|
std::size_t tileCnt) noexcept:
|
||||||
|
m_img(img),
|
||||||
|
m_idx(std::move(idx)) {
|
||||||
|
const unsigned bytesPerTile = m_img.bpp == 4 ? PixelsPerTile / 2 : PixelsPerTile;
|
||||||
|
m_insertPos = tileIdx * bytesPerTile;
|
||||||
|
m_insertCnt = tileCnt * bytesPerTile;
|
||||||
|
m_deletedPixels.resize(m_insertCnt);
|
||||||
|
// copy pixels to be erased
|
||||||
|
{
|
||||||
|
auto &s = m_img.getSubSheet(m_idx);
|
||||||
|
auto &p = s.pixels;
|
||||||
|
auto dst = m_deletedPixels.data();
|
||||||
|
auto src = p.data() + p.size() - m_insertCnt;
|
||||||
|
const auto sz = m_insertCnt * sizeof(decltype(p[0]));
|
||||||
|
ox_memcpy(dst, src, sz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InsertTilesCommand::redo() noexcept {
|
||||||
|
auto &s = m_img.getSubSheet(m_idx);
|
||||||
|
auto &p = s.pixels;
|
||||||
|
auto dstPos = m_insertPos + m_insertCnt;
|
||||||
|
const auto dst = p.data() + dstPos;
|
||||||
|
const auto src = p.data() + m_insertPos;
|
||||||
|
ox_memmove(dst, src, p.size() - dstPos);
|
||||||
|
ox_memset(src, 0, m_insertCnt * sizeof(decltype(p[0])));
|
||||||
|
}
|
||||||
|
|
||||||
|
void InsertTilesCommand::undo() noexcept {
|
||||||
|
auto &s = m_img.getSubSheet(m_idx);
|
||||||
|
auto &p = s.pixels;
|
||||||
|
const auto srcIdx = m_insertPos + m_insertCnt;
|
||||||
|
const auto src = p.data() + srcIdx;
|
||||||
|
const auto dst1 = p.data() + m_insertPos;
|
||||||
|
const auto dst2 = p.data() + p.size() - m_insertCnt;
|
||||||
|
const auto sz = p.size() - srcIdx;
|
||||||
|
ox_memmove(dst1, src, sz);
|
||||||
|
ox_memcpy(dst2, m_deletedPixels.data(), m_deletedPixels.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
int InsertTilesCommand::commandId() const noexcept {
|
||||||
|
return static_cast<int>(CommandId::InsertTile);
|
||||||
|
}
|
||||||
|
|
||||||
|
TileSheet::SubSheetIdx const &InsertTilesCommand::subsheetIdx() const noexcept {
|
||||||
|
return m_idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "commands.hpp"
|
||||||
|
|
||||||
|
namespace nostalgia::core {
|
||||||
|
|
||||||
|
class InsertTilesCommand: public TileSheetCommand {
|
||||||
|
private:
|
||||||
|
TileSheet &m_img;
|
||||||
|
TileSheet::SubSheetIdx m_idx;
|
||||||
|
std::size_t m_insertPos = 0;
|
||||||
|
std::size_t m_insertCnt = 0;
|
||||||
|
ox::Vector<uint8_t> m_deletedPixels = {};
|
||||||
|
|
||||||
|
public:
|
||||||
|
InsertTilesCommand(
|
||||||
|
TileSheet &img,
|
||||||
|
TileSheet::SubSheetIdx idx,
|
||||||
|
std::size_t tileIdx,
|
||||||
|
std::size_t tileCnt) noexcept;
|
||||||
|
|
||||||
|
void redo() noexcept final;
|
||||||
|
|
||||||
|
void undo() noexcept final;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
int commandId() const noexcept final;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
TileSheet::SubSheetIdx const&subsheetIdx() const noexcept override;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "palettechangecommand.hpp"
|
||||||
|
|
||||||
|
namespace nostalgia::core {
|
||||||
|
|
||||||
|
core::PaletteChangeCommand::PaletteChangeCommand(
|
||||||
|
TileSheet::SubSheetIdx idx,
|
||||||
|
TileSheet &img,
|
||||||
|
ox::CRStringView newPalette) noexcept:
|
||||||
|
m_img(img),
|
||||||
|
m_idx(std::move(idx)) {
|
||||||
|
m_oldPalette = m_img.defaultPalette;
|
||||||
|
m_newPalette = ox::FileAddress(ox::sfmt<ox::BString<43>>("uuid://{}", newPalette));
|
||||||
|
}
|
||||||
|
|
||||||
|
void PaletteChangeCommand::redo() noexcept {
|
||||||
|
m_img.defaultPalette = m_newPalette;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PaletteChangeCommand::undo() noexcept {
|
||||||
|
m_img.defaultPalette = m_oldPalette;
|
||||||
|
}
|
||||||
|
|
||||||
|
int PaletteChangeCommand::commandId() const noexcept {
|
||||||
|
return static_cast<int>(CommandId::PaletteChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
TileSheet::SubSheetIdx const &PaletteChangeCommand::subsheetIdx() const noexcept {
|
||||||
|
return m_idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "commands.hpp"
|
||||||
|
|
||||||
|
namespace nostalgia::core {
|
||||||
|
|
||||||
|
class PaletteChangeCommand: public TileSheetCommand {
|
||||||
|
private:
|
||||||
|
TileSheet &m_img;
|
||||||
|
TileSheet::SubSheetIdx m_idx;
|
||||||
|
ox::FileAddress m_oldPalette;
|
||||||
|
ox::FileAddress m_newPalette;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PaletteChangeCommand(
|
||||||
|
TileSheet::SubSheetIdx idx,
|
||||||
|
TileSheet &img,
|
||||||
|
ox::CRStringView newPalette) noexcept;
|
||||||
|
|
||||||
|
void redo() noexcept final;
|
||||||
|
|
||||||
|
void undo() noexcept final;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
int commandId() const noexcept final;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
TileSheet::SubSheetIdx const&subsheetIdx() const noexcept override;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "rmsubsheetcommand.hpp"
|
||||||
|
|
||||||
|
namespace nostalgia::core {
|
||||||
|
|
||||||
|
core::RmSubSheetCommand::RmSubSheetCommand(TileSheet &img, TileSheet::SubSheetIdx idx) noexcept:
|
||||||
|
m_img(img),
|
||||||
|
m_idx(std::move(idx)) {
|
||||||
|
m_parentIdx = idx;
|
||||||
|
m_parentIdx.pop_back();
|
||||||
|
auto &parent = m_img.getSubSheet(m_parentIdx);
|
||||||
|
m_sheet = parent.subsheets[*idx.back().value];
|
||||||
|
}
|
||||||
|
|
||||||
|
void RmSubSheetCommand::redo() noexcept {
|
||||||
|
auto &parent = m_img.getSubSheet(m_parentIdx);
|
||||||
|
oxLogError(parent.subsheets.erase(*m_idx.back().value).error);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RmSubSheetCommand::undo() noexcept {
|
||||||
|
auto &parent = m_img.getSubSheet(m_parentIdx);
|
||||||
|
auto i = *m_idx.back().value;
|
||||||
|
parent.subsheets.insert(i, m_sheet);
|
||||||
|
}
|
||||||
|
|
||||||
|
int RmSubSheetCommand::commandId() const noexcept {
|
||||||
|
return static_cast<int>(CommandId::RmSubSheet);
|
||||||
|
}
|
||||||
|
|
||||||
|
TileSheet::SubSheetIdx const &RmSubSheetCommand::subsheetIdx() const noexcept {
|
||||||
|
return m_idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "commands.hpp"
|
||||||
|
|
||||||
|
namespace nostalgia::core {
|
||||||
|
|
||||||
|
class RmSubSheetCommand: public TileSheetCommand {
|
||||||
|
private:
|
||||||
|
TileSheet &m_img;
|
||||||
|
TileSheet::SubSheetIdx m_idx;
|
||||||
|
TileSheet::SubSheetIdx m_parentIdx;
|
||||||
|
TileSheet::SubSheet m_sheet;
|
||||||
|
|
||||||
|
public:
|
||||||
|
RmSubSheetCommand(TileSheet &img, TileSheet::SubSheetIdx idx) noexcept;
|
||||||
|
|
||||||
|
void redo() noexcept final;
|
||||||
|
|
||||||
|
void undo() noexcept final;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
int commandId() const noexcept final;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
TileSheet::SubSheetIdx const&subsheetIdx() const noexcept override;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "updatesubsheetcommand.hpp"
|
||||||
|
|
||||||
|
namespace nostalgia::core {
|
||||||
|
|
||||||
|
core::UpdateSubSheetCommand::UpdateSubSheetCommand(
|
||||||
|
TileSheet &img,
|
||||||
|
TileSheet::SubSheetIdx idx,
|
||||||
|
ox::String name,
|
||||||
|
int cols,
|
||||||
|
int rows) noexcept:
|
||||||
|
m_img(img),
|
||||||
|
m_idx(std::move(idx)) {
|
||||||
|
m_sheet = m_img.getSubSheet(m_idx);
|
||||||
|
m_newName = std::move(name);
|
||||||
|
m_newCols = cols;
|
||||||
|
m_newRows = rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateSubSheetCommand::redo() noexcept {
|
||||||
|
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 UpdateSubSheetCommand::undo() noexcept {
|
||||||
|
auto &sheet = m_img.getSubSheet(m_idx);
|
||||||
|
sheet = m_sheet;
|
||||||
|
}
|
||||||
|
|
||||||
|
int UpdateSubSheetCommand::commandId() const noexcept {
|
||||||
|
return static_cast<int>(CommandId::UpdateSubSheet);
|
||||||
|
}
|
||||||
|
|
||||||
|
TileSheet::SubSheetIdx const &UpdateSubSheetCommand::subsheetIdx() const noexcept {
|
||||||
|
return m_idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "commands.hpp"
|
||||||
|
|
||||||
|
namespace nostalgia::core {
|
||||||
|
|
||||||
|
class UpdateSubSheetCommand: public TileSheetCommand {
|
||||||
|
private:
|
||||||
|
TileSheet &m_img;
|
||||||
|
TileSheet::SubSheetIdx m_idx;
|
||||||
|
TileSheet::SubSheet m_sheet;
|
||||||
|
ox::String m_newName;
|
||||||
|
int m_newCols = 0;
|
||||||
|
int m_newRows = 0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
UpdateSubSheetCommand(
|
||||||
|
TileSheet &img,
|
||||||
|
TileSheet::SubSheetIdx idx,
|
||||||
|
ox::String name,
|
||||||
|
int cols,
|
||||||
|
int rows) noexcept;
|
||||||
|
|
||||||
|
void redo() noexcept final;
|
||||||
|
|
||||||
|
void undo() noexcept final;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
int commandId() const noexcept final;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
TileSheet::SubSheetIdx const&subsheetIdx() const noexcept override;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,289 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include <ox/claw/read.hpp>
|
||||||
|
#include <ox/std/buffer.hpp>
|
||||||
|
#include <ox/std/memory.hpp>
|
||||||
|
|
||||||
|
#include <turbine/clipboard.hpp>
|
||||||
|
#include <keel/media.hpp>
|
||||||
|
|
||||||
|
#include <nostalgia/core/ptidxconv.hpp>
|
||||||
|
|
||||||
|
#include "commands/commands.hpp"
|
||||||
|
#include "commands/addsubsheetcommand.hpp"
|
||||||
|
#include "commands/cutpastecommand.hpp"
|
||||||
|
#include "commands/deletetilescommand.hpp"
|
||||||
|
#include "commands/drawcommand.hpp"
|
||||||
|
#include "commands/inserttilescommand.hpp"
|
||||||
|
#include "commands/palettechangecommand.hpp"
|
||||||
|
#include "commands/rmsubsheetcommand.hpp"
|
||||||
|
#include "commands/updatesubsheetcommand.hpp"
|
||||||
|
#include "tilesheeteditormodel.hpp"
|
||||||
|
|
||||||
|
namespace nostalgia::core {
|
||||||
|
|
||||||
|
const Palette TileSheetEditorModel::s_defaultPalette = {
|
||||||
|
.colors = ox::Vector<Color16>(128),
|
||||||
|
};
|
||||||
|
|
||||||
|
TileSheetEditorModel::TileSheetEditorModel(turbine::Context &ctx, ox::StringView path):
|
||||||
|
m_ctx(ctx),
|
||||||
|
m_path(path),
|
||||||
|
m_img(*readObj<TileSheet>(keelCtx(m_ctx), m_path).unwrapThrow()),
|
||||||
|
// ignore failure to load palette
|
||||||
|
m_pal(readObj<Palette>(keelCtx(m_ctx), m_img.defaultPalette).value) {
|
||||||
|
m_pal.updated.connect(this, &TileSheetEditorModel::markUpdated);
|
||||||
|
m_undoStack.changeTriggered.connect(this, &TileSheetEditorModel::markUpdatedCmdId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TileSheetEditorModel::cut() {
|
||||||
|
TileSheetClipboard blankCb;
|
||||||
|
auto cb = ox::make_unique<TileSheetClipboard>();
|
||||||
|
const auto s = activeSubSheet();
|
||||||
|
for (int y = m_selectionBounds.y; y <= m_selectionBounds.y2(); ++y) {
|
||||||
|
for (int x = m_selectionBounds.x; x <= m_selectionBounds.x2(); ++x) {
|
||||||
|
auto pt = ox::Point(x, y);
|
||||||
|
const auto idx = s->idx(pt);
|
||||||
|
const auto c = s->getPixel(m_img.bpp, idx);
|
||||||
|
pt.x -= m_selectionBounds.x;
|
||||||
|
pt.y -= m_selectionBounds.y;
|
||||||
|
cb->addPixel(pt, c);
|
||||||
|
blankCb.addPixel(pt, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const auto pt1 = m_selectionOrigin == ox::Point(-1, -1) ? ox::Point(0, 0) : m_selectionOrigin;
|
||||||
|
const auto pt2 = ox::Point(s->columns * TileWidth, s->rows * TileHeight);
|
||||||
|
turbine::setClipboardObject(m_ctx, std::move(cb));
|
||||||
|
pushCommand(ox::make<CutPasteCommand>(CommandId::Cut, m_img, m_activeSubsSheetIdx, pt1, pt2, blankCb));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TileSheetEditorModel::copy() {
|
||||||
|
auto cb = ox::make_unique<TileSheetClipboard>();
|
||||||
|
for (int y = m_selectionBounds.y; y <= m_selectionBounds.y2(); ++y) {
|
||||||
|
for (int x = m_selectionBounds.x; x <= m_selectionBounds.x2(); ++x) {
|
||||||
|
auto pt = ox::Point(x, y);
|
||||||
|
const auto s = activeSubSheet();
|
||||||
|
const auto idx = s->idx(pt);
|
||||||
|
const auto c = s->getPixel(m_img.bpp, idx);
|
||||||
|
pt.x -= m_selectionBounds.x;
|
||||||
|
pt.y -= m_selectionBounds.y;
|
||||||
|
cb->addPixel(pt, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
turbine::setClipboardObject(m_ctx, std::move(cb));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TileSheetEditorModel::paste() {
|
||||||
|
auto [cb, err] = turbine::getClipboardObject<TileSheetClipboard>(m_ctx);
|
||||||
|
if (err) {
|
||||||
|
oxLogError(err);
|
||||||
|
oxErrf("Could not read clipboard: {}", toStr(err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto s = activeSubSheet();
|
||||||
|
const auto pt1 = m_selectionOrigin == ox::Point(-1, -1) ? ox::Point(0, 0) : m_selectionOrigin;
|
||||||
|
const auto pt2 = ox::Point(s->columns * TileWidth, s->rows * TileHeight);
|
||||||
|
pushCommand(ox::make<CutPasteCommand>(CommandId::Paste, m_img, m_activeSubsSheetIdx, pt1, pt2, *cb));
|
||||||
|
}
|
||||||
|
|
||||||
|
ox::StringView TileSheetEditorModel::palPath() const noexcept {
|
||||||
|
auto [path, err] = m_img.defaultPalette.getPath();
|
||||||
|
if (err) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
constexpr ox::StringView uuidPrefix = "uuid://";
|
||||||
|
if (ox::beginsWith(path, uuidPrefix)) {
|
||||||
|
auto uuid = ox::StringView(path.data() + uuidPrefix.bytes(), path.bytes() - uuidPrefix.bytes());
|
||||||
|
auto out = keelCtx(m_ctx).uuidToPath.at(uuid);
|
||||||
|
if (out.error) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return *out.value;
|
||||||
|
} else {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ox::Error TileSheetEditorModel::setPalette(ox::String const&path) noexcept {
|
||||||
|
oxRequire(uuid, keelCtx(m_ctx).pathToUuid.at(path));
|
||||||
|
pushCommand(ox::make<PaletteChangeCommand>(activeSubSheetIdx(), m_img, uuid->toString()));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void TileSheetEditorModel::drawCommand(ox::Point const&pt, std::size_t palIdx) noexcept {
|
||||||
|
const auto &activeSubSheet = m_img.getSubSheet(m_activeSubsSheetIdx);
|
||||||
|
if (pt.x >= activeSubSheet.columns * TileWidth || pt.y >= activeSubSheet.rows * TileHeight) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto idx = activeSubSheet.idx(pt);
|
||||||
|
if (m_ongoingDrawCommand) {
|
||||||
|
m_updated = m_updated || m_ongoingDrawCommand->append(idx);
|
||||||
|
} else if (activeSubSheet.getPixel(m_img.bpp, idx) != palIdx) {
|
||||||
|
pushCommand(ox::make<DrawCommand>(m_img, m_activeSubsSheetIdx, idx, static_cast<int>(palIdx)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TileSheetEditorModel::endDrawCommand() noexcept {
|
||||||
|
m_ongoingDrawCommand = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TileSheetEditorModel::addSubsheet(TileSheet::SubSheetIdx const&parentIdx) noexcept {
|
||||||
|
pushCommand(ox::make<AddSubSheetCommand>(m_img, parentIdx));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TileSheetEditorModel::rmSubsheet(TileSheet::SubSheetIdx const&idx) noexcept {
|
||||||
|
pushCommand(ox::make<RmSubSheetCommand>(m_img, idx));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TileSheetEditorModel::insertTiles(TileSheet::SubSheetIdx const&idx, std::size_t tileIdx, std::size_t tileCnt) noexcept {
|
||||||
|
pushCommand(ox::make<InsertTilesCommand>(m_img, idx, tileIdx, tileCnt));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TileSheetEditorModel::deleteTiles(TileSheet::SubSheetIdx const&idx, std::size_t tileIdx, std::size_t tileCnt) noexcept {
|
||||||
|
pushCommand(ox::make<DeleteTilesCommand>(m_img, idx, tileIdx, tileCnt));
|
||||||
|
}
|
||||||
|
|
||||||
|
ox::Error TileSheetEditorModel::updateSubsheet(TileSheet::SubSheetIdx const&idx, const ox::StringView &name, int cols, int rows) noexcept {
|
||||||
|
pushCommand(ox::make<UpdateSubSheetCommand>(m_img, idx, ox::String(name), cols, rows));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void TileSheetEditorModel::setActiveSubsheet(TileSheet::SubSheetIdx const&idx) noexcept {
|
||||||
|
m_activeSubsSheetIdx = idx;
|
||||||
|
this->activeSubsheetChanged.emit(m_activeSubsSheetIdx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TileSheetEditorModel::fill(ox::Point const&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 (pt.x >= s.columns * TileWidth || pt.y >= s.rows * TileHeight) {
|
||||||
|
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(ox::make<DrawCommand>(m_img, m_activeSubsSheetIdx, idxList, palIdx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TileSheetEditorModel::select(ox::Point const&pt) noexcept {
|
||||||
|
if (!m_selectionOngoing) {
|
||||||
|
m_selectionOrigin = pt;
|
||||||
|
m_selectionOngoing = true;
|
||||||
|
m_selectionBounds = {pt, pt};
|
||||||
|
m_updated = true;
|
||||||
|
} else if (m_selectionBounds.pt2() != pt) {
|
||||||
|
m_selectionBounds = {m_selectionOrigin, pt};
|
||||||
|
m_updated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TileSheetEditorModel::completeSelection() noexcept {
|
||||||
|
m_selectionOngoing = false;
|
||||||
|
auto s = activeSubSheet();
|
||||||
|
auto pt = m_selectionBounds.pt2();
|
||||||
|
pt.x = ox::min(s->columns * TileWidth, pt.x);
|
||||||
|
pt.y = ox::min(s->rows * TileHeight, pt.y);
|
||||||
|
m_selectionBounds.setPt2(pt);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TileSheetEditorModel::clearSelection() noexcept {
|
||||||
|
m_updated = true;
|
||||||
|
m_selectionOrigin = {-1, -1};
|
||||||
|
m_selectionBounds = {{-1, -1}, {-1, -1}};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TileSheetEditorModel::updated() const noexcept {
|
||||||
|
return m_updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
ox::Error TileSheetEditorModel::markUpdatedCmdId(const studio::UndoCommand *cmd) noexcept {
|
||||||
|
m_updated = true;
|
||||||
|
const auto cmdId = cmd->commandId();
|
||||||
|
if (static_cast<CommandId>(cmdId) == CommandId::PaletteChange) {
|
||||||
|
oxReturnError(readObj<Palette>(keelCtx(m_ctx), m_img.defaultPalette).moveTo(&m_pal));
|
||||||
|
paletteChanged.emit();
|
||||||
|
}
|
||||||
|
auto tsCmd = dynamic_cast<const TileSheetCommand*>(cmd);
|
||||||
|
auto idx = m_img.validateSubSheetIdx(tsCmd->subsheetIdx());
|
||||||
|
if (idx != m_activeSubsSheetIdx) {
|
||||||
|
setActiveSubsheet(idx);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
ox::Error TileSheetEditorModel::markUpdated() noexcept {
|
||||||
|
m_updated = true;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void TileSheetEditorModel::ackUpdate() noexcept {
|
||||||
|
m_updated = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ox::Error TileSheetEditorModel::saveFile() noexcept {
|
||||||
|
const auto sctx = applicationData<studio::StudioContext>(m_ctx);
|
||||||
|
return sctx->project->writeObj(m_path, m_img);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TileSheetEditorModel::pixelSelected(std::size_t idx) const noexcept {
|
||||||
|
const auto s = activeSubSheet();
|
||||||
|
const auto pt = idxToPt(static_cast<int>(idx), s->columns);
|
||||||
|
return m_selectionBounds.contains(pt);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TileSheetEditorModel::getFillPixels(bool *pixels, ox::Point const&pt, int oldColor) const noexcept {
|
||||||
|
const auto &activeSubSheet = *this->activeSubSheet();
|
||||||
|
const auto tileIdx = [activeSubSheet](const ox::Point &pt) noexcept {
|
||||||
|
return ptToIdx(pt, activeSubSheet.columns) / PixelsPerTile;
|
||||||
|
};
|
||||||
|
// get points
|
||||||
|
const auto leftPt = pt + ox::Point(-1, 0);
|
||||||
|
const auto rightPt = pt + ox::Point(1, 0);
|
||||||
|
const auto topPt = pt + ox::Point(0, -1);
|
||||||
|
const auto bottomPt = pt + ox::Point(0, 1);
|
||||||
|
// calculate indices
|
||||||
|
const auto idx = ptToIdx(pt, activeSubSheet.columns);
|
||||||
|
const auto leftIdx = ptToIdx(leftPt, activeSubSheet.columns);
|
||||||
|
const auto rightIdx = ptToIdx(rightPt, activeSubSheet.columns);
|
||||||
|
const auto topIdx = ptToIdx(topPt, activeSubSheet.columns);
|
||||||
|
const auto bottomIdx = ptToIdx(bottomPt, activeSubSheet.columns);
|
||||||
|
const auto tile = tileIdx(pt);
|
||||||
|
// mark pixels to update
|
||||||
|
pixels[idx % PixelsPerTile] = true;
|
||||||
|
if (!pixels[leftIdx % PixelsPerTile] && tile == tileIdx(leftPt) && activeSubSheet.getPixel(m_img.bpp, leftIdx) == oldColor) {
|
||||||
|
getFillPixels(pixels, leftPt, oldColor);
|
||||||
|
}
|
||||||
|
if (!pixels[rightIdx % PixelsPerTile] && tile == tileIdx(rightPt) && activeSubSheet.getPixel(m_img.bpp, rightIdx) == oldColor) {
|
||||||
|
getFillPixels(pixels, rightPt, oldColor);
|
||||||
|
}
|
||||||
|
if (!pixels[topIdx % PixelsPerTile] && tile == tileIdx(topPt) && activeSubSheet.getPixel(m_img.bpp, topIdx) == oldColor) {
|
||||||
|
getFillPixels(pixels, topPt, oldColor);
|
||||||
|
}
|
||||||
|
if (!pixels[bottomIdx % PixelsPerTile] && tile == tileIdx(bottomPt) && activeSubSheet.getPixel(m_img.bpp, bottomIdx) == oldColor) {
|
||||||
|
getFillPixels(pixels, bottomPt, oldColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TileSheetEditorModel::pushCommand(studio::UndoCommand *cmd) noexcept {
|
||||||
|
m_undoStack.push(cmd);
|
||||||
|
m_ongoingDrawCommand = dynamic_cast<DrawCommand*>(cmd);
|
||||||
|
m_updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,824 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
#include <ox/claw/read.hpp>
|
|
||||||
#include <ox/std/algorithm.hpp>
|
|
||||||
#include <ox/std/buffer.hpp>
|
|
||||||
#include <ox/std/memory.hpp>
|
|
||||||
|
|
||||||
#include <turbine/clipboard.hpp>
|
|
||||||
#include <keel/media.hpp>
|
|
||||||
|
|
||||||
#include <nostalgia/core/ptidxconv.hpp>
|
|
||||||
|
|
||||||
#include "tilesheeteditormodel.hpp"
|
|
||||||
|
|
||||||
namespace nostalgia::core {
|
|
||||||
|
|
||||||
const Palette TileSheetEditorModel::s_defaultPalette = {
|
|
||||||
.colors = ox::Vector<Color16>(128),
|
|
||||||
};
|
|
||||||
|
|
||||||
class TileSheetClipboard: public turbine::ClipboardObject<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;
|
|
||||||
ox::Point pt;
|
|
||||||
constexpr Pixel(uint16_t pColorIdx, ox::Point pPt) noexcept {
|
|
||||||
colorIdx = pColorIdx;
|
|
||||||
pt = pPt;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
protected:
|
|
||||||
ox::Vector<Pixel> m_pixels;
|
|
||||||
|
|
||||||
public:
|
|
||||||
constexpr void addPixel(ox::Point const&pt, uint16_t colorIdx) noexcept {
|
|
||||||
m_pixels.emplace_back(colorIdx, pt);
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]]
|
|
||||||
constexpr ox::Vector<Pixel> const&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,
|
|
||||||
DeleteTile = 4,
|
|
||||||
InsertTile = 4,
|
|
||||||
UpdateSubSheet = 5,
|
|
||||||
Cut = 6,
|
|
||||||
Paste = 7,
|
|
||||||
PaletteChange = 8,
|
|
||||||
};
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
class TileSheetCommand: public studio::UndoCommand {
|
|
||||||
public:
|
|
||||||
[[nodiscard]]
|
|
||||||
virtual TileSheet::SubSheetIdx const&subsheetIdx() const noexcept = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
class DrawCommand: public TileSheetCommand {
|
|
||||||
private:
|
|
||||||
struct Change {
|
|
||||||
uint32_t idx = 0;
|
|
||||||
uint16_t oldPalIdx = 0;
|
|
||||||
constexpr Change(uint32_t pIdx, uint16_t pOldPalIdx) noexcept {
|
|
||||||
idx = pIdx;
|
|
||||||
oldPalIdx = pOldPalIdx;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
TileSheet &m_img;
|
|
||||||
TileSheet::SubSheetIdx m_subSheetIdx;
|
|
||||||
ox::Vector<Change, 8> m_changes;
|
|
||||||
int m_palIdx = 0;
|
|
||||||
|
|
||||||
public:
|
|
||||||
DrawCommand(
|
|
||||||
TileSheet &img,
|
|
||||||
TileSheet::SubSheetIdx subSheetIdx,
|
|
||||||
std::size_t idx,
|
|
||||||
int palIdx) noexcept:
|
|
||||||
m_img(img),
|
|
||||||
m_subSheetIdx(std::move(subSheetIdx)) {
|
|
||||||
auto &subsheet = m_img.getSubSheet(m_subSheetIdx);
|
|
||||||
m_changes.emplace_back(static_cast<uint32_t>(idx), subsheet.getPixel(m_img.bpp, idx));
|
|
||||||
m_palIdx = palIdx;
|
|
||||||
}
|
|
||||||
|
|
||||||
DrawCommand(
|
|
||||||
TileSheet &img,
|
|
||||||
TileSheet::SubSheetIdx subSheetIdx,
|
|
||||||
const ox::Vector<std::size_t> &idxList,
|
|
||||||
int palIdx) noexcept:
|
|
||||||
m_img(img),
|
|
||||||
m_subSheetIdx(std::move(subSheetIdx)) {
|
|
||||||
auto &subsheet = m_img.getSubSheet(m_subSheetIdx);
|
|
||||||
for (const auto idx : idxList) {
|
|
||||||
m_changes.emplace_back(static_cast<uint32_t>(idx), subsheet.getPixel(m_img.bpp, idx));
|
|
||||||
}
|
|
||||||
m_palIdx = palIdx;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool 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 = ox::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(static_cast<uint32_t>(idx), subsheet.getPixel(m_img.bpp, idx));
|
|
||||||
subsheet.setPixel(m_img.bpp, idx, static_cast<uint8_t>(m_palIdx));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr auto append(const 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, static_cast<uint8_t>(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, static_cast<uint8_t>(c.oldPalIdx));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]]
|
|
||||||
int commandId() const noexcept final {
|
|
||||||
return static_cast<int>(CommandId::Draw);
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]]
|
|
||||||
TileSheet::SubSheetIdx const&subsheetIdx() const noexcept override {
|
|
||||||
return m_subSheetIdx;
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
template<CommandId CommandId>
|
|
||||||
class CutPasteCommand: public TileSheetCommand {
|
|
||||||
private:
|
|
||||||
struct Change {
|
|
||||||
uint32_t idx = 0;
|
|
||||||
uint16_t newPalIdx = 0;
|
|
||||||
uint16_t oldPalIdx = 0;
|
|
||||||
constexpr Change(uint32_t pIdx, uint16_t pNewPalIdx, uint16_t pOldPalIdx) noexcept {
|
|
||||||
idx = pIdx;
|
|
||||||
newPalIdx = pNewPalIdx;
|
|
||||||
oldPalIdx = pOldPalIdx;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
TileSheet &m_img;
|
|
||||||
TileSheet::SubSheetIdx m_subSheetIdx;
|
|
||||||
ox::Vector<Change> m_changes;
|
|
||||||
|
|
||||||
public:
|
|
||||||
constexpr CutPasteCommand(
|
|
||||||
TileSheet &img,
|
|
||||||
TileSheet::SubSheetIdx subSheetIdx,
|
|
||||||
ox::Point const&dstStart,
|
|
||||||
ox::Point const&dstEnd,
|
|
||||||
TileSheetClipboard const&cb) noexcept:
|
|
||||||
m_img(img),
|
|
||||||
m_subSheetIdx(std::move(subSheetIdx)) {
|
|
||||||
const auto &subsheet = m_img.getSubSheet(m_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(static_cast<uint32_t>(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, static_cast<uint8_t>(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, static_cast<uint8_t>(c.oldPalIdx));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]]
|
|
||||||
int commandId() const noexcept final {
|
|
||||||
return static_cast<int>(CommandId);
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]]
|
|
||||||
TileSheet::SubSheetIdx const&subsheetIdx() const noexcept override {
|
|
||||||
return m_subSheetIdx;
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
class AddSubSheetCommand: public TileSheetCommand {
|
|
||||||
private:
|
|
||||||
TileSheet &m_img;
|
|
||||||
TileSheet::SubSheetIdx m_parentIdx;
|
|
||||||
ox::Vector<TileSheet::SubSheetIdx, 2> m_addedSheets;
|
|
||||||
|
|
||||||
public:
|
|
||||||
AddSubSheetCommand(TileSheet &img, TileSheet::SubSheetIdx parentIdx) noexcept:
|
|
||||||
m_img(img), m_parentIdx(std::move(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(0u);
|
|
||||||
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(m_img.idIt++, ox::sfmt("Subsheet {}", i), 1, 1, m_img.bpp);
|
|
||||||
} else {
|
|
||||||
parent.subsheets.emplace_back(m_img.idIt++, "Subsheet 0", parent.columns, parent.rows, std::move(parent.pixels));
|
|
||||||
parent.rows = 0;
|
|
||||||
parent.columns = 0;
|
|
||||||
parent.subsheets.emplace_back(m_img.idIt++, "Subsheet 1", 1, 1, m_img.bpp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]]
|
|
||||||
TileSheet::SubSheetIdx const&subsheetIdx() const noexcept override {
|
|
||||||
return m_parentIdx;
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
class RmSubSheetCommand: public TileSheetCommand {
|
|
||||||
private:
|
|
||||||
TileSheet &m_img;
|
|
||||||
TileSheet::SubSheetIdx m_idx;
|
|
||||||
TileSheet::SubSheetIdx m_parentIdx;
|
|
||||||
TileSheet::SubSheet m_sheet;
|
|
||||||
|
|
||||||
public:
|
|
||||||
RmSubSheetCommand(TileSheet &img, TileSheet::SubSheetIdx idx) noexcept:
|
|
||||||
m_img(img),
|
|
||||||
m_idx(std::move(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);
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]]
|
|
||||||
TileSheet::SubSheetIdx const&subsheetIdx() const noexcept override {
|
|
||||||
return m_idx;
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
class InsertTilesCommand: public TileSheetCommand {
|
|
||||||
private:
|
|
||||||
TileSheet &m_img;
|
|
||||||
TileSheet::SubSheetIdx m_idx;
|
|
||||||
std::size_t m_insertPos = 0;
|
|
||||||
std::size_t m_insertCnt = 0;
|
|
||||||
ox::Vector<uint8_t> m_deletedPixels = {};
|
|
||||||
|
|
||||||
public:
|
|
||||||
InsertTilesCommand(
|
|
||||||
TileSheet &img,
|
|
||||||
TileSheet::SubSheetIdx idx,
|
|
||||||
std::size_t tileIdx,
|
|
||||||
std::size_t tileCnt) noexcept:
|
|
||||||
m_img(img),
|
|
||||||
m_idx(std::move(idx)) {
|
|
||||||
const unsigned bytesPerTile = m_img.bpp == 4 ? PixelsPerTile / 2 : PixelsPerTile;
|
|
||||||
m_insertPos = tileIdx * bytesPerTile;
|
|
||||||
m_insertCnt = tileCnt * bytesPerTile;
|
|
||||||
m_deletedPixels.resize(m_insertCnt);
|
|
||||||
// copy pixels to be erased
|
|
||||||
{
|
|
||||||
auto &s = m_img.getSubSheet(m_idx);
|
|
||||||
auto &p = s.pixels;
|
|
||||||
auto dst = m_deletedPixels.data();
|
|
||||||
auto src = p.data() + p.size() - m_insertCnt;
|
|
||||||
const auto sz = m_insertCnt * sizeof(decltype(p[0]));
|
|
||||||
ox_memcpy(dst, src, sz);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void redo() noexcept final {
|
|
||||||
auto &s = m_img.getSubSheet(m_idx);
|
|
||||||
auto &p = s.pixels;
|
|
||||||
auto dstPos = m_insertPos + m_insertCnt;
|
|
||||||
const auto dst = p.data() + dstPos;
|
|
||||||
const auto src = p.data() + m_insertPos;
|
|
||||||
ox_memmove(dst, src, p.size() - dstPos);
|
|
||||||
ox_memset(src, 0, m_insertCnt * sizeof(decltype(p[0])));
|
|
||||||
}
|
|
||||||
|
|
||||||
void undo() noexcept final {
|
|
||||||
auto &s = m_img.getSubSheet(m_idx);
|
|
||||||
auto &p = s.pixels;
|
|
||||||
const auto srcIdx = m_insertPos + m_insertCnt;
|
|
||||||
const auto src = p.data() + srcIdx;
|
|
||||||
const auto dst1 = p.data() + m_insertPos;
|
|
||||||
const auto dst2 = p.data() + p.size() - m_insertCnt;
|
|
||||||
const auto sz = p.size() - srcIdx;
|
|
||||||
ox_memmove(dst1, src, sz);
|
|
||||||
ox_memcpy(dst2, m_deletedPixels.data(), m_deletedPixels.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]]
|
|
||||||
int commandId() const noexcept final {
|
|
||||||
return static_cast<int>(CommandId::InsertTile);
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]]
|
|
||||||
TileSheet::SubSheetIdx const&subsheetIdx() const noexcept override {
|
|
||||||
return m_idx;
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
class DeleteTilesCommand: public TileSheetCommand {
|
|
||||||
private:
|
|
||||||
TileSheet &m_img;
|
|
||||||
TileSheet::SubSheetIdx m_idx;
|
|
||||||
std::size_t m_deletePos = 0;
|
|
||||||
std::size_t m_deleteSz = 0;
|
|
||||||
ox::Vector<uint8_t> m_deletedPixels = {};
|
|
||||||
|
|
||||||
public:
|
|
||||||
DeleteTilesCommand(
|
|
||||||
TileSheet &img,
|
|
||||||
TileSheet::SubSheetIdx idx,
|
|
||||||
std::size_t tileIdx,
|
|
||||||
std::size_t tileCnt) noexcept:
|
|
||||||
m_img(img),
|
|
||||||
m_idx(std::move(idx)) {
|
|
||||||
const unsigned bytesPerTile = m_img.bpp == 4 ? PixelsPerTile / 2 : PixelsPerTile;
|
|
||||||
m_deletePos = tileIdx * bytesPerTile;
|
|
||||||
m_deleteSz = tileCnt * bytesPerTile;
|
|
||||||
m_deletedPixels.resize(m_deleteSz);
|
|
||||||
// copy pixels to be erased
|
|
||||||
{
|
|
||||||
auto &s = m_img.getSubSheet(m_idx);
|
|
||||||
auto &p = s.pixels;
|
|
||||||
auto dst = m_deletedPixels.data();
|
|
||||||
auto src = p.data() + m_deletePos;
|
|
||||||
const auto sz = m_deleteSz * sizeof(decltype(p[0]));
|
|
||||||
ox_memcpy(dst, src, sz);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void redo() noexcept final {
|
|
||||||
auto &s = m_img.getSubSheet(m_idx);
|
|
||||||
auto &p = s.pixels;
|
|
||||||
auto srcPos = m_deletePos + m_deleteSz;
|
|
||||||
const auto src = p.data() + srcPos;
|
|
||||||
const auto dst1 = p.data() + m_deletePos;
|
|
||||||
const auto dst2 = p.data() + (p.size() - m_deleteSz);
|
|
||||||
ox_memmove(dst1, src, p.size() - srcPos);
|
|
||||||
ox_memset(dst2, 0, m_deleteSz * sizeof(decltype(p[0])));
|
|
||||||
}
|
|
||||||
|
|
||||||
void undo() noexcept final {
|
|
||||||
auto &s = m_img.getSubSheet(m_idx);
|
|
||||||
auto &p = s.pixels;
|
|
||||||
const auto src = p.data() + m_deletePos;
|
|
||||||
const auto dst1 = p.data() + m_deletePos + m_deleteSz;
|
|
||||||
const auto dst2 = src;
|
|
||||||
const auto sz = p.size() - m_deletePos - m_deleteSz;
|
|
||||||
ox_memmove(dst1, src, sz);
|
|
||||||
ox_memcpy(dst2, m_deletedPixels.data(), m_deletedPixels.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]]
|
|
||||||
int commandId() const noexcept final {
|
|
||||||
return static_cast<int>(CommandId::DeleteTile);
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]]
|
|
||||||
TileSheet::SubSheetIdx const&subsheetIdx() const noexcept override {
|
|
||||||
return m_idx;
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
class UpdateSubSheetCommand: public TileSheetCommand {
|
|
||||||
private:
|
|
||||||
TileSheet &m_img;
|
|
||||||
TileSheet::SubSheetIdx m_idx;
|
|
||||||
TileSheet::SubSheet m_sheet;
|
|
||||||
ox::String m_newName;
|
|
||||||
int m_newCols = 0;
|
|
||||||
int m_newRows = 0;
|
|
||||||
|
|
||||||
public:
|
|
||||||
UpdateSubSheetCommand(
|
|
||||||
TileSheet &img,
|
|
||||||
TileSheet::SubSheetIdx idx,
|
|
||||||
ox::String name,
|
|
||||||
int cols,
|
|
||||||
int rows) noexcept:
|
|
||||||
m_img(img),
|
|
||||||
m_idx(std::move(idx)) {
|
|
||||||
m_sheet = m_img.getSubSheet(m_idx);
|
|
||||||
m_newName = std::move(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);
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]]
|
|
||||||
TileSheet::SubSheetIdx const&subsheetIdx() const noexcept override {
|
|
||||||
return m_idx;
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
class PaletteChangeCommand: public TileSheetCommand {
|
|
||||||
private:
|
|
||||||
TileSheet &m_img;
|
|
||||||
TileSheet::SubSheetIdx m_idx;
|
|
||||||
ox::FileAddress m_oldPalette;
|
|
||||||
ox::FileAddress m_newPalette;
|
|
||||||
|
|
||||||
public:
|
|
||||||
PaletteChangeCommand(
|
|
||||||
TileSheet::SubSheetIdx idx,
|
|
||||||
TileSheet &img,
|
|
||||||
ox::CRStringView newPalette) noexcept:
|
|
||||||
m_img(img),
|
|
||||||
m_idx(std::move(idx)) {
|
|
||||||
m_oldPalette = m_img.defaultPalette;
|
|
||||||
m_newPalette = ox::FileAddress(ox::sfmt<ox::BString<43>>("uuid://{}", newPalette));
|
|
||||||
}
|
|
||||||
|
|
||||||
void redo() noexcept final {
|
|
||||||
m_img.defaultPalette = m_newPalette;
|
|
||||||
}
|
|
||||||
|
|
||||||
void undo() noexcept final {
|
|
||||||
m_img.defaultPalette = m_oldPalette;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]]
|
|
||||||
int commandId() const noexcept final {
|
|
||||||
return static_cast<int>(CommandId::PaletteChange);
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]]
|
|
||||||
TileSheet::SubSheetIdx const&subsheetIdx() const noexcept override {
|
|
||||||
return m_idx;
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
TileSheetEditorModel::TileSheetEditorModel(turbine::Context &ctx, ox::StringView path):
|
|
||||||
m_ctx(ctx),
|
|
||||||
m_path(path),
|
|
||||||
m_img(*readObj<TileSheet>(keelCtx(m_ctx), m_path).unwrapThrow()),
|
|
||||||
// ignore failure to load palette
|
|
||||||
m_pal(readObj<Palette>(keelCtx(m_ctx), m_img.defaultPalette).value) {
|
|
||||||
m_pal.updated.connect(this, &TileSheetEditorModel::markUpdated);
|
|
||||||
m_undoStack.changeTriggered.connect(this, &TileSheetEditorModel::markUpdatedCmdId);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TileSheetEditorModel::cut() {
|
|
||||||
TileSheetClipboard blankCb;
|
|
||||||
auto cb = ox::make_unique<TileSheetClipboard>();
|
|
||||||
const auto s = activeSubSheet();
|
|
||||||
for (int y = m_selectionBounds.y; y <= m_selectionBounds.y2(); ++y) {
|
|
||||||
for (int x = m_selectionBounds.x; x <= m_selectionBounds.x2(); ++x) {
|
|
||||||
auto pt = ox::Point(x, y);
|
|
||||||
const auto idx = s->idx(pt);
|
|
||||||
const auto c = s->getPixel(m_img.bpp, idx);
|
|
||||||
pt.x -= m_selectionBounds.x;
|
|
||||||
pt.y -= m_selectionBounds.y;
|
|
||||||
cb->addPixel(pt, c);
|
|
||||||
blankCb.addPixel(pt, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const auto pt1 = m_selectionOrigin == ox::Point(-1, -1) ? ox::Point(0, 0) : m_selectionOrigin;
|
|
||||||
const auto pt2 = ox::Point(s->columns * TileWidth, s->rows * TileHeight);
|
|
||||||
turbine::setClipboardObject(m_ctx, std::move(cb));
|
|
||||||
pushCommand(ox::make<CutPasteCommand<CommandId::Cut>>(m_img, m_activeSubsSheetIdx, pt1, pt2, blankCb));
|
|
||||||
}
|
|
||||||
|
|
||||||
void TileSheetEditorModel::copy() {
|
|
||||||
auto cb = ox::make_unique<TileSheetClipboard>();
|
|
||||||
for (int y = m_selectionBounds.y; y <= m_selectionBounds.y2(); ++y) {
|
|
||||||
for (int x = m_selectionBounds.x; x <= m_selectionBounds.x2(); ++x) {
|
|
||||||
auto pt = ox::Point(x, y);
|
|
||||||
const auto s = activeSubSheet();
|
|
||||||
const auto idx = s->idx(pt);
|
|
||||||
const auto c = s->getPixel(m_img.bpp, idx);
|
|
||||||
pt.x -= m_selectionBounds.x;
|
|
||||||
pt.y -= m_selectionBounds.y;
|
|
||||||
cb->addPixel(pt, c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
turbine::setClipboardObject(m_ctx, std::move(cb));
|
|
||||||
}
|
|
||||||
|
|
||||||
void TileSheetEditorModel::paste() {
|
|
||||||
auto [cb, err] = turbine::getClipboardObject<TileSheetClipboard>(m_ctx);
|
|
||||||
if (err) {
|
|
||||||
oxLogError(err);
|
|
||||||
oxErrf("Could not read clipboard: {}", toStr(err));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const auto s = activeSubSheet();
|
|
||||||
const auto pt1 = m_selectionOrigin == ox::Point(-1, -1) ? ox::Point(0, 0) : m_selectionOrigin;
|
|
||||||
const auto pt2 = ox::Point(s->columns * TileWidth, s->rows * TileHeight);
|
|
||||||
pushCommand(ox::make<CutPasteCommand<CommandId::Paste>>(m_img, m_activeSubsSheetIdx, pt1, pt2, *cb));
|
|
||||||
}
|
|
||||||
|
|
||||||
ox::StringView TileSheetEditorModel::palPath() const noexcept {
|
|
||||||
auto [path, err] = m_img.defaultPalette.getPath();
|
|
||||||
if (err) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
constexpr ox::StringView uuidPrefix = "uuid://";
|
|
||||||
if (ox::beginsWith(path, uuidPrefix)) {
|
|
||||||
auto uuid = ox::StringView(path.data() + uuidPrefix.bytes(), path.bytes() - uuidPrefix.bytes());
|
|
||||||
auto out = keelCtx(m_ctx).uuidToPath.at(uuid);
|
|
||||||
if (out.error) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
return *out.value;
|
|
||||||
} else {
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ox::Error TileSheetEditorModel::setPalette(ox::String const&path) noexcept {
|
|
||||||
oxRequire(uuid, keelCtx(m_ctx).pathToUuid.at(path));
|
|
||||||
pushCommand(ox::make<PaletteChangeCommand>(activeSubSheetIdx(), m_img, uuid->toString()));
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
void TileSheetEditorModel::drawCommand(ox::Point const&pt, std::size_t palIdx) noexcept {
|
|
||||||
const auto &activeSubSheet = m_img.getSubSheet(m_activeSubsSheetIdx);
|
|
||||||
if (pt.x >= activeSubSheet.columns * TileWidth || pt.y >= activeSubSheet.rows * TileHeight) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const auto idx = activeSubSheet.idx(pt);
|
|
||||||
if (m_ongoingDrawCommand) {
|
|
||||||
m_updated = m_updated || m_ongoingDrawCommand->append(idx);
|
|
||||||
} else if (activeSubSheet.getPixel(m_img.bpp, idx) != palIdx) {
|
|
||||||
pushCommand(ox::make<DrawCommand>(m_img, m_activeSubsSheetIdx, idx, static_cast<int>(palIdx)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void TileSheetEditorModel::endDrawCommand() noexcept {
|
|
||||||
m_ongoingDrawCommand = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TileSheetEditorModel::addSubsheet(TileSheet::SubSheetIdx const&parentIdx) noexcept {
|
|
||||||
pushCommand(ox::make<AddSubSheetCommand>(m_img, parentIdx));
|
|
||||||
}
|
|
||||||
|
|
||||||
void TileSheetEditorModel::rmSubsheet(TileSheet::SubSheetIdx const&idx) noexcept {
|
|
||||||
pushCommand(ox::make<RmSubSheetCommand>(m_img, idx));
|
|
||||||
}
|
|
||||||
|
|
||||||
void TileSheetEditorModel::insertTiles(TileSheet::SubSheetIdx const&idx, std::size_t tileIdx, std::size_t tileCnt) noexcept {
|
|
||||||
pushCommand(ox::make<InsertTilesCommand>(m_img, idx, tileIdx, tileCnt));
|
|
||||||
}
|
|
||||||
|
|
||||||
void TileSheetEditorModel::deleteTiles(TileSheet::SubSheetIdx const&idx, std::size_t tileIdx, std::size_t tileCnt) noexcept {
|
|
||||||
pushCommand(ox::make<DeleteTilesCommand>(m_img, idx, tileIdx, tileCnt));
|
|
||||||
}
|
|
||||||
|
|
||||||
ox::Error TileSheetEditorModel::updateSubsheet(TileSheet::SubSheetIdx const&idx, const ox::StringView &name, int cols, int rows) noexcept {
|
|
||||||
pushCommand(ox::make<UpdateSubSheetCommand>(m_img, idx, ox::String(name), cols, rows));
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
void TileSheetEditorModel::setActiveSubsheet(TileSheet::SubSheetIdx const&idx) noexcept {
|
|
||||||
m_activeSubsSheetIdx = idx;
|
|
||||||
this->activeSubsheetChanged.emit(m_activeSubsSheetIdx);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TileSheetEditorModel::fill(ox::Point const&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 (pt.x >= s.columns * TileWidth || pt.y >= s.rows * TileHeight) {
|
|
||||||
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(ox::make<DrawCommand>(m_img, m_activeSubsSheetIdx, idxList, palIdx));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void TileSheetEditorModel::select(ox::Point const&pt) noexcept {
|
|
||||||
if (!m_selectionOngoing) {
|
|
||||||
m_selectionOrigin = pt;
|
|
||||||
m_selectionOngoing = true;
|
|
||||||
m_selectionBounds = {pt, pt};
|
|
||||||
m_updated = true;
|
|
||||||
} else if (m_selectionBounds.pt2() != pt) {
|
|
||||||
m_selectionBounds = {m_selectionOrigin, pt};
|
|
||||||
m_updated = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void TileSheetEditorModel::completeSelection() noexcept {
|
|
||||||
m_selectionOngoing = false;
|
|
||||||
auto s = activeSubSheet();
|
|
||||||
auto pt = m_selectionBounds.pt2();
|
|
||||||
pt.x = ox::min(s->columns * TileWidth, pt.x);
|
|
||||||
pt.y = ox::min(s->rows * TileHeight, pt.y);
|
|
||||||
m_selectionBounds.setPt2(pt);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TileSheetEditorModel::clearSelection() noexcept {
|
|
||||||
m_updated = true;
|
|
||||||
m_selectionOrigin = {-1, -1};
|
|
||||||
m_selectionBounds = {{-1, -1}, {-1, -1}};
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TileSheetEditorModel::updated() const noexcept {
|
|
||||||
return m_updated;
|
|
||||||
}
|
|
||||||
|
|
||||||
ox::Error TileSheetEditorModel::markUpdatedCmdId(const studio::UndoCommand *cmd) noexcept {
|
|
||||||
m_updated = true;
|
|
||||||
const auto cmdId = cmd->commandId();
|
|
||||||
if (static_cast<CommandId>(cmdId) == CommandId::PaletteChange) {
|
|
||||||
oxReturnError(readObj<Palette>(keelCtx(m_ctx), m_img.defaultPalette).moveTo(&m_pal));
|
|
||||||
paletteChanged.emit();
|
|
||||||
}
|
|
||||||
auto tsCmd = dynamic_cast<const TileSheetCommand*>(cmd);
|
|
||||||
auto idx = m_img.validateSubSheetIdx(tsCmd->subsheetIdx());
|
|
||||||
if (idx != m_activeSubsSheetIdx) {
|
|
||||||
setActiveSubsheet(idx);
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
ox::Error TileSheetEditorModel::markUpdated() noexcept {
|
|
||||||
m_updated = true;
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
void TileSheetEditorModel::ackUpdate() noexcept {
|
|
||||||
m_updated = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ox::Error TileSheetEditorModel::saveFile() noexcept {
|
|
||||||
const auto sctx = applicationData<studio::StudioContext>(m_ctx);
|
|
||||||
return sctx->project->writeObj(m_path, m_img);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TileSheetEditorModel::pixelSelected(std::size_t idx) const noexcept {
|
|
||||||
const auto s = activeSubSheet();
|
|
||||||
const auto pt = idxToPt(static_cast<int>(idx), s->columns);
|
|
||||||
return m_selectionBounds.contains(pt);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TileSheetEditorModel::getFillPixels(bool *pixels, ox::Point const&pt, int oldColor) const noexcept {
|
|
||||||
const auto &activeSubSheet = *this->activeSubSheet();
|
|
||||||
const auto tileIdx = [activeSubSheet](const ox::Point &pt) noexcept {
|
|
||||||
return ptToIdx(pt, activeSubSheet.columns) / PixelsPerTile;
|
|
||||||
};
|
|
||||||
// get points
|
|
||||||
const auto leftPt = pt + ox::Point(-1, 0);
|
|
||||||
const auto rightPt = pt + ox::Point(1, 0);
|
|
||||||
const auto topPt = pt + ox::Point(0, -1);
|
|
||||||
const auto bottomPt = pt + ox::Point(0, 1);
|
|
||||||
// calculate indices
|
|
||||||
const auto idx = ptToIdx(pt, activeSubSheet.columns);
|
|
||||||
const auto leftIdx = ptToIdx(leftPt, activeSubSheet.columns);
|
|
||||||
const auto rightIdx = ptToIdx(rightPt, activeSubSheet.columns);
|
|
||||||
const auto topIdx = ptToIdx(topPt, activeSubSheet.columns);
|
|
||||||
const auto bottomIdx = ptToIdx(bottomPt, activeSubSheet.columns);
|
|
||||||
const auto tile = tileIdx(pt);
|
|
||||||
// mark pixels to update
|
|
||||||
pixels[idx % PixelsPerTile] = true;
|
|
||||||
if (!pixels[leftIdx % PixelsPerTile] && tile == tileIdx(leftPt) && activeSubSheet.getPixel(m_img.bpp, leftIdx) == oldColor) {
|
|
||||||
getFillPixels(pixels, leftPt, oldColor);
|
|
||||||
}
|
|
||||||
if (!pixels[rightIdx % PixelsPerTile] && tile == tileIdx(rightPt) && activeSubSheet.getPixel(m_img.bpp, rightIdx) == oldColor) {
|
|
||||||
getFillPixels(pixels, rightPt, oldColor);
|
|
||||||
}
|
|
||||||
if (!pixels[topIdx % PixelsPerTile] && tile == tileIdx(topPt) && activeSubSheet.getPixel(m_img.bpp, topIdx) == oldColor) {
|
|
||||||
getFillPixels(pixels, topPt, oldColor);
|
|
||||||
}
|
|
||||||
if (!pixels[bottomIdx % PixelsPerTile] && tile == tileIdx(bottomPt) && activeSubSheet.getPixel(m_img.bpp, bottomIdx) == oldColor) {
|
|
||||||
getFillPixels(pixels, bottomPt, oldColor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void TileSheetEditorModel::pushCommand(studio::UndoCommand *cmd) noexcept {
|
|
||||||
m_undoStack.push(cmd);
|
|
||||||
m_ongoingDrawCommand = dynamic_cast<DrawCommand*>(cmd);
|
|
||||||
m_updated = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user