From 7418a7126a4e41699c77bb26a1212848f087685c Mon Sep 17 00:00:00 2001 From: Gary Talent Date: Sun, 10 Dec 2023 01:48:24 -0600 Subject: [PATCH] [nostalgia/core/studio] Restructure core studio for maintainability --- .../modules/core/src/studio/CMakeLists.txt | 16 +- .../src/studio/paletteeditor/CMakeLists.txt | 5 + .../paletteeditor-imgui.cpp | 0 .../paletteeditor-imgui.hpp | 0 .../{ => paletteeditor}/paletteeditor.cpp | 0 .../{ => paletteeditor}/paletteeditor.hpp | 0 .../modules/core/src/studio/studiomodule.cpp | 4 +- .../src/studio/tilesheeteditor/CMakeLists.txt | 14 + .../tilesheeteditor/commands/CMakeLists.txt | 11 + .../commands/addsubsheetcommand.cpp | 63 ++ .../commands/addsubsheetcommand.hpp | 31 + .../tilesheeteditor/commands/commands.hpp | 39 + .../commands/cutpastecommand.cpp | 66 ++ .../commands/cutpastecommand.hpp | 83 ++ .../commands/deletetilescommand.cpp | 61 ++ .../commands/deletetilescommand.hpp | 37 + .../tilesheeteditor/commands/drawcommand.cpp | 81 ++ .../tilesheeteditor/commands/drawcommand.hpp | 54 ++ .../commands/inserttilescommand.cpp | 61 ++ .../commands/inserttilescommand.hpp | 37 + .../commands/palettechangecommand.cpp | 35 + .../commands/palettechangecommand.hpp | 35 + .../commands/rmsubsheetcommand.cpp | 37 + .../commands/rmsubsheetcommand.hpp | 32 + .../commands/updatesubsheetcommand.cpp | 44 + .../commands/updatesubsheetcommand.hpp | 39 + .../tilesheeteditor-imgui.cpp | 0 .../tilesheeteditor-imgui.hpp | 0 .../tilesheeteditor/tilesheeteditormodel.cpp | 289 ++++++ .../tilesheeteditormodel.hpp | 0 .../tilesheeteditorview.cpp | 0 .../tilesheeteditorview.hpp | 0 .../tilesheetpixelgrid.cpp | 0 .../tilesheetpixelgrid.hpp | 0 .../{ => tilesheeteditor}/tilesheetpixels.cpp | 0 .../{ => tilesheeteditor}/tilesheetpixels.hpp | 0 .../core/src/studio/tilesheeteditormodel.cpp | 824 ------------------ 37 files changed, 1161 insertions(+), 837 deletions(-) create mode 100644 src/nostalgia/modules/core/src/studio/paletteeditor/CMakeLists.txt rename src/nostalgia/modules/core/src/studio/{ => paletteeditor}/paletteeditor-imgui.cpp (100%) rename src/nostalgia/modules/core/src/studio/{ => paletteeditor}/paletteeditor-imgui.hpp (100%) rename src/nostalgia/modules/core/src/studio/{ => paletteeditor}/paletteeditor.cpp (100%) rename src/nostalgia/modules/core/src/studio/{ => paletteeditor}/paletteeditor.hpp (100%) create mode 100644 src/nostalgia/modules/core/src/studio/tilesheeteditor/CMakeLists.txt create mode 100644 src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/CMakeLists.txt create mode 100644 src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/addsubsheetcommand.cpp create mode 100644 src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/addsubsheetcommand.hpp create mode 100644 src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/commands.hpp create mode 100644 src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/cutpastecommand.cpp create mode 100644 src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/cutpastecommand.hpp create mode 100644 src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/deletetilescommand.cpp create mode 100644 src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/deletetilescommand.hpp create mode 100644 src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/drawcommand.cpp create mode 100644 src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/drawcommand.hpp create mode 100644 src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/inserttilescommand.cpp create mode 100644 src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/inserttilescommand.hpp create mode 100644 src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/palettechangecommand.cpp create mode 100644 src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/palettechangecommand.hpp create mode 100644 src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/rmsubsheetcommand.cpp create mode 100644 src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/rmsubsheetcommand.hpp create mode 100644 src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/updatesubsheetcommand.cpp create mode 100644 src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/updatesubsheetcommand.hpp rename src/nostalgia/modules/core/src/studio/{ => tilesheeteditor}/tilesheeteditor-imgui.cpp (100%) rename src/nostalgia/modules/core/src/studio/{ => tilesheeteditor}/tilesheeteditor-imgui.hpp (100%) create mode 100644 src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditormodel.cpp rename src/nostalgia/modules/core/src/studio/{ => tilesheeteditor}/tilesheeteditormodel.hpp (100%) rename src/nostalgia/modules/core/src/studio/{ => tilesheeteditor}/tilesheeteditorview.cpp (100%) rename src/nostalgia/modules/core/src/studio/{ => tilesheeteditor}/tilesheeteditorview.hpp (100%) rename src/nostalgia/modules/core/src/studio/{ => tilesheeteditor}/tilesheetpixelgrid.cpp (100%) rename src/nostalgia/modules/core/src/studio/{ => tilesheeteditor}/tilesheetpixelgrid.hpp (100%) rename src/nostalgia/modules/core/src/studio/{ => tilesheeteditor}/tilesheetpixels.cpp (100%) rename src/nostalgia/modules/core/src/studio/{ => tilesheeteditor}/tilesheetpixels.hpp (100%) delete mode 100644 src/nostalgia/modules/core/src/studio/tilesheeteditormodel.cpp diff --git a/src/nostalgia/modules/core/src/studio/CMakeLists.txt b/src/nostalgia/modules/core/src/studio/CMakeLists.txt index f73c7eda..df7bfadb 100644 --- a/src/nostalgia/modules/core/src/studio/CMakeLists.txt +++ b/src/nostalgia/modules/core/src/studio/CMakeLists.txt @@ -1,17 +1,9 @@ -add_library( - NostalgiaCore-Studio - paletteeditor.cpp - tilesheeteditorview.cpp - tilesheeteditormodel.cpp - tilesheetpixelgrid.cpp - tilesheetpixels.cpp -) +add_library(NostalgiaCore-Studio) add_library( NostalgiaCore-Studio-ImGui studiomodule.cpp - paletteeditor-imgui.cpp - tilesheeteditor-imgui.cpp + tilesheeteditor/tilesheeteditor-imgui.cpp ) target_link_libraries( @@ -24,7 +16,6 @@ target_link_libraries( NostalgiaCore-Studio-ImGui PUBLIC NostalgiaCore-Studio Studio - lodepng ) install( @@ -34,3 +25,6 @@ install( LIBRARY DESTINATION ${NOSTALGIA_DIST_MODULE} ) + +add_subdirectory(paletteeditor) +add_subdirectory(tilesheeteditor) diff --git a/src/nostalgia/modules/core/src/studio/paletteeditor/CMakeLists.txt b/src/nostalgia/modules/core/src/studio/paletteeditor/CMakeLists.txt new file mode 100644 index 00000000..671cf09d --- /dev/null +++ b/src/nostalgia/modules/core/src/studio/paletteeditor/CMakeLists.txt @@ -0,0 +1,5 @@ +target_sources( + NostalgiaCore-Studio PRIVATE + paletteeditor.cpp + paletteeditor-imgui.cpp +) diff --git a/src/nostalgia/modules/core/src/studio/paletteeditor-imgui.cpp b/src/nostalgia/modules/core/src/studio/paletteeditor/paletteeditor-imgui.cpp similarity index 100% rename from src/nostalgia/modules/core/src/studio/paletteeditor-imgui.cpp rename to src/nostalgia/modules/core/src/studio/paletteeditor/paletteeditor-imgui.cpp diff --git a/src/nostalgia/modules/core/src/studio/paletteeditor-imgui.hpp b/src/nostalgia/modules/core/src/studio/paletteeditor/paletteeditor-imgui.hpp similarity index 100% rename from src/nostalgia/modules/core/src/studio/paletteeditor-imgui.hpp rename to src/nostalgia/modules/core/src/studio/paletteeditor/paletteeditor-imgui.hpp diff --git a/src/nostalgia/modules/core/src/studio/paletteeditor.cpp b/src/nostalgia/modules/core/src/studio/paletteeditor/paletteeditor.cpp similarity index 100% rename from src/nostalgia/modules/core/src/studio/paletteeditor.cpp rename to src/nostalgia/modules/core/src/studio/paletteeditor/paletteeditor.cpp diff --git a/src/nostalgia/modules/core/src/studio/paletteeditor.hpp b/src/nostalgia/modules/core/src/studio/paletteeditor/paletteeditor.hpp similarity index 100% rename from src/nostalgia/modules/core/src/studio/paletteeditor.hpp rename to src/nostalgia/modules/core/src/studio/paletteeditor/paletteeditor.hpp diff --git a/src/nostalgia/modules/core/src/studio/studiomodule.cpp b/src/nostalgia/modules/core/src/studio/studiomodule.cpp index 15f5c80f..c7350339 100644 --- a/src/nostalgia/modules/core/src/studio/studiomodule.cpp +++ b/src/nostalgia/modules/core/src/studio/studiomodule.cpp @@ -6,8 +6,8 @@ #include -#include "paletteeditor-imgui.hpp" -#include "tilesheeteditor-imgui.hpp" +#include "paletteeditor/paletteeditor-imgui.hpp" +#include "tilesheeteditor/tilesheeteditor-imgui.hpp" namespace nostalgia::core { diff --git a/src/nostalgia/modules/core/src/studio/tilesheeteditor/CMakeLists.txt b/src/nostalgia/modules/core/src/studio/tilesheeteditor/CMakeLists.txt new file mode 100644 index 00000000..bc92afd0 --- /dev/null +++ b/src/nostalgia/modules/core/src/studio/tilesheeteditor/CMakeLists.txt @@ -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) diff --git a/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/CMakeLists.txt b/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/CMakeLists.txt new file mode 100644 index 00000000..7096243e --- /dev/null +++ b/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/CMakeLists.txt @@ -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 +) diff --git a/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/addsubsheetcommand.cpp b/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/addsubsheetcommand.cpp new file mode 100644 index 00000000..c5c1ec29 --- /dev/null +++ b/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/addsubsheetcommand.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(CommandId::AddSubSheet); +} + +TileSheet::SubSheetIdx const &AddSubSheetCommand::subsheetIdx() const noexcept { + return m_parentIdx; +} + +} diff --git a/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/addsubsheetcommand.hpp b/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/addsubsheetcommand.hpp new file mode 100644 index 00000000..f6542314 --- /dev/null +++ b/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/addsubsheetcommand.hpp @@ -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 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; + +}; + +} \ No newline at end of file diff --git a/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/commands.hpp b/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/commands.hpp new file mode 100644 index 00000000..d6418c6c --- /dev/null +++ b/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/commands.hpp @@ -0,0 +1,39 @@ +/* + * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#pragma once + +#include +#include + +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(c) == i; +} + +constexpr bool operator==(int i, CommandId c) noexcept { + return static_cast(c) == i; +} + +class TileSheetCommand: public studio::UndoCommand { + public: + [[nodiscard]] + virtual TileSheet::SubSheetIdx const&subsheetIdx() const noexcept = 0; +}; + +} \ No newline at end of file diff --git a/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/cutpastecommand.cpp b/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/cutpastecommand.cpp new file mode 100644 index 00000000..4adf02f4 --- /dev/null +++ b/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/cutpastecommand.cpp @@ -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::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(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(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(c.oldPalIdx)); + } +} + +int CutPasteCommand::commandId() const noexcept { + return static_cast(m_commandId); +} + +TileSheet::SubSheetIdx const &CutPasteCommand::subsheetIdx() const noexcept { + return m_subSheetIdx; +} + +} diff --git a/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/cutpastecommand.hpp b/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/cutpastecommand.hpp new file mode 100644 index 00000000..3003913b --- /dev/null +++ b/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/cutpastecommand.hpp @@ -0,0 +1,83 @@ +/* + * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ +#pragma once + +#include + +#include "commands.hpp" + +namespace nostalgia::core { + +class TileSheetClipboard: public turbine::ClipboardObject { + 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 m_pixels; + + public: + void addPixel(ox::Point const&pt, uint16_t colorIdx) noexcept; + + [[nodiscard]] + ox::Vector 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 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; + +}; + +} \ No newline at end of file diff --git a/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/deletetilescommand.cpp b/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/deletetilescommand.cpp new file mode 100644 index 00000000..313b0d89 --- /dev/null +++ b/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/deletetilescommand.cpp @@ -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(CommandId::DeleteTile); +} + +TileSheet::SubSheetIdx const &DeleteTilesCommand::subsheetIdx() const noexcept { + return m_idx; +} + +} diff --git a/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/deletetilescommand.hpp b/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/deletetilescommand.hpp new file mode 100644 index 00000000..9d6c262f --- /dev/null +++ b/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/deletetilescommand.hpp @@ -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 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; + +}; + +} \ No newline at end of file diff --git a/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/drawcommand.cpp b/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/drawcommand.cpp new file mode 100644 index 00000000..0becb669 --- /dev/null +++ b/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/drawcommand.cpp @@ -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(idx), subsheet.getPixel(m_img.bpp, idx)); + m_palIdx = palIdx; +} + +DrawCommand::DrawCommand( + TileSheet &img, + TileSheet::SubSheetIdx subSheetIdx, + const ox::Vector &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(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(idx), subsheet.getPixel(m_img.bpp, idx)); + subsheet.setPixel(m_img.bpp, idx, static_cast(m_palIdx)); + return true; + } + } + return false; +} + +bool DrawCommand::append(const ox::Vector &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(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(c.oldPalIdx)); + } +} + +int DrawCommand::commandId() const noexcept { + return static_cast(CommandId::Draw); +} + +TileSheet::SubSheetIdx const &DrawCommand::subsheetIdx() const noexcept { + return m_subSheetIdx; +} + +} diff --git a/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/drawcommand.hpp b/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/drawcommand.hpp new file mode 100644 index 00000000..8634bb51 --- /dev/null +++ b/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/drawcommand.hpp @@ -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 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 &idxList, + int palIdx) noexcept; + + bool append(std::size_t idx) noexcept; + + bool append(const ox::Vector &idxList) noexcept; + + void redo() noexcept final; + + void undo() noexcept final; + + [[nodiscard]] + int commandId() const noexcept final; + + [[nodiscard]] + TileSheet::SubSheetIdx const&subsheetIdx() const noexcept override; + +}; + +} \ No newline at end of file diff --git a/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/inserttilescommand.cpp b/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/inserttilescommand.cpp new file mode 100644 index 00000000..95065ac4 --- /dev/null +++ b/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/inserttilescommand.cpp @@ -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(CommandId::InsertTile); +} + +TileSheet::SubSheetIdx const &InsertTilesCommand::subsheetIdx() const noexcept { + return m_idx; +} + +} diff --git a/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/inserttilescommand.hpp b/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/inserttilescommand.hpp new file mode 100644 index 00000000..2177636b --- /dev/null +++ b/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/inserttilescommand.hpp @@ -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 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; + +}; + +} \ No newline at end of file diff --git a/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/palettechangecommand.cpp b/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/palettechangecommand.cpp new file mode 100644 index 00000000..193bde36 --- /dev/null +++ b/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/palettechangecommand.cpp @@ -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>("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(CommandId::PaletteChange); +} + +TileSheet::SubSheetIdx const &PaletteChangeCommand::subsheetIdx() const noexcept { + return m_idx; +} + +} diff --git a/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/palettechangecommand.hpp b/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/palettechangecommand.hpp new file mode 100644 index 00000000..00bba351 --- /dev/null +++ b/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/palettechangecommand.hpp @@ -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; + +}; + +} \ No newline at end of file diff --git a/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/rmsubsheetcommand.cpp b/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/rmsubsheetcommand.cpp new file mode 100644 index 00000000..5303de68 --- /dev/null +++ b/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/rmsubsheetcommand.cpp @@ -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(CommandId::RmSubSheet); +} + +TileSheet::SubSheetIdx const &RmSubSheetCommand::subsheetIdx() const noexcept { + return m_idx; +} + +} diff --git a/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/rmsubsheetcommand.hpp b/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/rmsubsheetcommand.hpp new file mode 100644 index 00000000..149c7ee3 --- /dev/null +++ b/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/rmsubsheetcommand.hpp @@ -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; + +}; + +} \ No newline at end of file diff --git a/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/updatesubsheetcommand.cpp b/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/updatesubsheetcommand.cpp new file mode 100644 index 00000000..740979de --- /dev/null +++ b/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/updatesubsheetcommand.cpp @@ -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(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(CommandId::UpdateSubSheet); +} + +TileSheet::SubSheetIdx const &UpdateSubSheetCommand::subsheetIdx() const noexcept { + return m_idx; +} + +} diff --git a/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/updatesubsheetcommand.hpp b/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/updatesubsheetcommand.hpp new file mode 100644 index 00000000..036e4e28 --- /dev/null +++ b/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/updatesubsheetcommand.hpp @@ -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; + +}; + +} \ No newline at end of file diff --git a/src/nostalgia/modules/core/src/studio/tilesheeteditor-imgui.cpp b/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp similarity index 100% rename from src/nostalgia/modules/core/src/studio/tilesheeteditor-imgui.cpp rename to src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp diff --git a/src/nostalgia/modules/core/src/studio/tilesheeteditor-imgui.hpp b/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditor-imgui.hpp similarity index 100% rename from src/nostalgia/modules/core/src/studio/tilesheeteditor-imgui.hpp rename to src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditor-imgui.hpp diff --git a/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditormodel.cpp b/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditormodel.cpp new file mode 100644 index 00000000..44b92756 --- /dev/null +++ b/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditormodel.cpp @@ -0,0 +1,289 @@ +/* + * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + + +#include +#include +#include + +#include +#include + +#include + +#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(128), +}; + +TileSheetEditorModel::TileSheetEditorModel(turbine::Context &ctx, ox::StringView path): + m_ctx(ctx), + m_path(path), + m_img(*readObj(keelCtx(m_ctx), m_path).unwrapThrow()), + // ignore failure to load palette + m_pal(readObj(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(); + 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(CommandId::Cut, m_img, m_activeSubsSheetIdx, pt1, pt2, blankCb)); +} + +void TileSheetEditorModel::copy() { + auto cb = ox::make_unique(); + 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(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(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(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(m_img, m_activeSubsSheetIdx, idx, static_cast(palIdx))); + } +} + +void TileSheetEditorModel::endDrawCommand() noexcept { + m_ongoingDrawCommand = nullptr; +} + +void TileSheetEditorModel::addSubsheet(TileSheet::SubSheetIdx const&parentIdx) noexcept { + pushCommand(ox::make(m_img, parentIdx)); +} + +void TileSheetEditorModel::rmSubsheet(TileSheet::SubSheetIdx const&idx) noexcept { + pushCommand(ox::make(m_img, idx)); +} + +void TileSheetEditorModel::insertTiles(TileSheet::SubSheetIdx const&idx, std::size_t tileIdx, std::size_t tileCnt) noexcept { + pushCommand(ox::make(m_img, idx, tileIdx, tileCnt)); +} + +void TileSheetEditorModel::deleteTiles(TileSheet::SubSheetIdx const&idx, std::size_t tileIdx, std::size_t tileCnt) noexcept { + pushCommand(ox::make(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(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 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 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(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(cmdId) == CommandId::PaletteChange) { + oxReturnError(readObj(keelCtx(m_ctx), m_img.defaultPalette).moveTo(&m_pal)); + paletteChanged.emit(); + } + auto tsCmd = dynamic_cast(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(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(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(cmd); + m_updated = true; +} + +} diff --git a/src/nostalgia/modules/core/src/studio/tilesheeteditormodel.hpp b/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditormodel.hpp similarity index 100% rename from src/nostalgia/modules/core/src/studio/tilesheeteditormodel.hpp rename to src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditormodel.hpp diff --git a/src/nostalgia/modules/core/src/studio/tilesheeteditorview.cpp b/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditorview.cpp similarity index 100% rename from src/nostalgia/modules/core/src/studio/tilesheeteditorview.cpp rename to src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditorview.cpp diff --git a/src/nostalgia/modules/core/src/studio/tilesheeteditorview.hpp b/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditorview.hpp similarity index 100% rename from src/nostalgia/modules/core/src/studio/tilesheeteditorview.hpp rename to src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditorview.hpp diff --git a/src/nostalgia/modules/core/src/studio/tilesheetpixelgrid.cpp b/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheetpixelgrid.cpp similarity index 100% rename from src/nostalgia/modules/core/src/studio/tilesheetpixelgrid.cpp rename to src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheetpixelgrid.cpp diff --git a/src/nostalgia/modules/core/src/studio/tilesheetpixelgrid.hpp b/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheetpixelgrid.hpp similarity index 100% rename from src/nostalgia/modules/core/src/studio/tilesheetpixelgrid.hpp rename to src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheetpixelgrid.hpp diff --git a/src/nostalgia/modules/core/src/studio/tilesheetpixels.cpp b/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheetpixels.cpp similarity index 100% rename from src/nostalgia/modules/core/src/studio/tilesheetpixels.cpp rename to src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheetpixels.cpp diff --git a/src/nostalgia/modules/core/src/studio/tilesheetpixels.hpp b/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheetpixels.hpp similarity index 100% rename from src/nostalgia/modules/core/src/studio/tilesheetpixels.hpp rename to src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheetpixels.hpp diff --git a/src/nostalgia/modules/core/src/studio/tilesheeteditormodel.cpp b/src/nostalgia/modules/core/src/studio/tilesheeteditormodel.cpp deleted file mode 100644 index 20b22c20..00000000 --- a/src/nostalgia/modules/core/src/studio/tilesheeteditormodel.cpp +++ /dev/null @@ -1,824 +0,0 @@ -/* - * Copyright 2016 - 2023 Gary Talent (gary@drinkingtea.net). All rights reserved. - */ - - -#include -#include -#include -#include - -#include -#include - -#include - -#include "tilesheeteditormodel.hpp" - -namespace nostalgia::core { - -const Palette TileSheetEditorModel::s_defaultPalette = { - .colors = ox::Vector(128), -}; - -class TileSheetClipboard: public turbine::ClipboardObject { - 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 m_pixels; - - public: - constexpr void addPixel(ox::Point const&pt, uint16_t colorIdx) noexcept { - m_pixels.emplace_back(colorIdx, pt); - } - - [[nodiscard]] - constexpr ox::Vector 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(c) == i; -} - -constexpr bool operator==(int i, CommandId c) noexcept { - return static_cast(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 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(idx), subsheet.getPixel(m_img.bpp, idx)); - m_palIdx = palIdx; - } - - DrawCommand( - TileSheet &img, - TileSheet::SubSheetIdx subSheetIdx, - const ox::Vector &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(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(idx), subsheet.getPixel(m_img.bpp, idx)); - subsheet.setPixel(m_img.bpp, idx, static_cast(m_palIdx)); - return true; - } - } - return false; - } - - constexpr auto append(const ox::Vector &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(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(c.oldPalIdx)); - } - } - - [[nodiscard]] - int commandId() const noexcept final { - return static_cast(CommandId::Draw); - } - - [[nodiscard]] - TileSheet::SubSheetIdx const&subsheetIdx() const noexcept override { - return m_subSheetIdx; - } - -}; - -template -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 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(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(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(c.oldPalIdx)); - } - } - - [[nodiscard]] - int commandId() const noexcept final { - return static_cast(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 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(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(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 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(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 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(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(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(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>("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(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(keelCtx(m_ctx), m_path).unwrapThrow()), - // ignore failure to load palette - m_pal(readObj(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(); - 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>(m_img, m_activeSubsSheetIdx, pt1, pt2, blankCb)); -} - -void TileSheetEditorModel::copy() { - auto cb = ox::make_unique(); - 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(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>(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(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(m_img, m_activeSubsSheetIdx, idx, static_cast(palIdx))); - } -} - -void TileSheetEditorModel::endDrawCommand() noexcept { - m_ongoingDrawCommand = nullptr; -} - -void TileSheetEditorModel::addSubsheet(TileSheet::SubSheetIdx const&parentIdx) noexcept { - pushCommand(ox::make(m_img, parentIdx)); -} - -void TileSheetEditorModel::rmSubsheet(TileSheet::SubSheetIdx const&idx) noexcept { - pushCommand(ox::make(m_img, idx)); -} - -void TileSheetEditorModel::insertTiles(TileSheet::SubSheetIdx const&idx, std::size_t tileIdx, std::size_t tileCnt) noexcept { - pushCommand(ox::make(m_img, idx, tileIdx, tileCnt)); -} - -void TileSheetEditorModel::deleteTiles(TileSheet::SubSheetIdx const&idx, std::size_t tileIdx, std::size_t tileCnt) noexcept { - pushCommand(ox::make(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(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 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 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(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(cmdId) == CommandId::PaletteChange) { - oxReturnError(readObj(keelCtx(m_ctx), m_img.defaultPalette).moveTo(&m_pal)); - paletteChanged.emit(); - } - auto tsCmd = dynamic_cast(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(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(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(cmd); - m_updated = true; -} - -}