[nostalgia/studio] Add export option to tilesheet editor
This commit is contained in:
parent
2448bdcc82
commit
0adfaa7901
@ -32,13 +32,13 @@ enum class TileSheetSpace {
|
|||||||
struct NostalgiaPalette {
|
struct NostalgiaPalette {
|
||||||
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.NostalgiaPalette";
|
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.NostalgiaPalette";
|
||||||
static constexpr auto TypeVersion = 1;
|
static constexpr auto TypeVersion = 1;
|
||||||
ox::Vector<Color16> colors;
|
ox::Vector<Color16> colors = {};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Palette {
|
struct Palette {
|
||||||
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.Palette";
|
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.Palette";
|
||||||
static constexpr auto TypeVersion = 1;
|
static constexpr auto TypeVersion = 1;
|
||||||
ox::Vector<Color16> colors;
|
ox::Vector<Color16> colors = {};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Predecessor to TileSheet, kept for backward compatibility
|
// Predecessor to TileSheet, kept for backward compatibility
|
||||||
@ -51,7 +51,7 @@ struct NostalgiaGraphic {
|
|||||||
int columns = 1;
|
int columns = 1;
|
||||||
ox::FileAddress defaultPalette;
|
ox::FileAddress defaultPalette;
|
||||||
Palette pal;
|
Palette pal;
|
||||||
ox::Vector<uint8_t> pixels;
|
ox::Vector<uint8_t> pixels = {};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TileSheet {
|
struct TileSheet {
|
||||||
@ -64,7 +64,7 @@ struct TileSheet {
|
|||||||
int columns = 1;
|
int columns = 1;
|
||||||
int rows = 1;
|
int rows = 1;
|
||||||
ox::Vector<SubSheet> subsheets;
|
ox::Vector<SubSheet> subsheets;
|
||||||
ox::Vector<uint8_t> pixels;
|
ox::Vector<uint8_t> pixels = {};
|
||||||
|
|
||||||
constexpr SubSheet() noexcept = default;
|
constexpr SubSheet() noexcept = default;
|
||||||
constexpr SubSheet(const SubSheet &other) noexcept {
|
constexpr SubSheet(const SubSheet &other) noexcept {
|
||||||
@ -109,10 +109,34 @@ struct TileSheet {
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
auto idx(const geo::Point &pt) const noexcept {
|
auto idx(const geo::Point &pt) const noexcept {
|
||||||
return ptToIdx(pt, columns);
|
return ptToIdx(pt, columns);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads all pixels of this sheet or its children into the given pixel list
|
||||||
|
* @param pixels
|
||||||
|
*/
|
||||||
|
void readPixelsTo(ox::Vector<uint8_t> *pPixels, int8_t bpp) const noexcept {
|
||||||
|
if (subsheets.size()) {
|
||||||
|
for (auto &s: subsheets) {
|
||||||
|
s.readPixelsTo(pPixels);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (bpp == 4) {
|
||||||
|
for (auto p: this->pixels) {
|
||||||
|
pPixels->emplace_back(p & 0b1111);
|
||||||
|
pPixels->emplace_back(p >> 4);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (auto p: this->pixels) {
|
||||||
|
pPixels->emplace_back(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads all pixels of this sheet or its children into the given pixel list
|
* Reads all pixels of this sheet or its children into the given pixel list
|
||||||
* @param pixels
|
* @param pixels
|
||||||
@ -158,8 +182,8 @@ struct TileSheet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
constexpr auto getPixel(int8_t bpp, std::size_t idx) const noexcept {
|
constexpr auto getPixel(int8_t pBpp, std::size_t idx) const noexcept {
|
||||||
if (bpp == 4) {
|
if (pBpp == 4) {
|
||||||
return getPixel4Bpp(idx);
|
return getPixel4Bpp(idx);
|
||||||
} else {
|
} else {
|
||||||
return getPixel8Bpp(idx);
|
return getPixel8Bpp(idx);
|
||||||
@ -179,13 +203,13 @@ struct TileSheet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
constexpr auto getPixel(int8_t bpp, const geo::Point &pt) const noexcept {
|
constexpr auto getPixel(int8_t pBpp, const geo::Point &pt) const noexcept {
|
||||||
const auto idx = ptToIdx(pt, columns);
|
const auto idx = ptToIdx(pt, columns);
|
||||||
return getPixel(bpp, idx);
|
return getPixel(pBpp, idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr auto walkPixels(int8_t bpp, auto callback) const noexcept {
|
constexpr auto walkPixels(int8_t pBpp, auto callback) const noexcept {
|
||||||
if (bpp == 4) {
|
if (pBpp == 4) {
|
||||||
for (std::size_t i = 0; i < pixels.size(); ++i) {
|
for (std::size_t i = 0; i < pixels.size(); ++i) {
|
||||||
const auto colorIdx1 = pixels[i] & 0xF;
|
const auto colorIdx1 = pixels[i] & 0xF;
|
||||||
const auto colorIdx2 = pixels[i] >> 4;
|
const auto colorIdx2 = pixels[i] >> 4;
|
||||||
@ -200,9 +224,9 @@ struct TileSheet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr void setPixel(int8_t bpp, uint64_t idx, uint8_t palIdx) noexcept {
|
constexpr void setPixel(int8_t pBpp, uint64_t idx, uint8_t palIdx) noexcept {
|
||||||
auto &pixel = this->pixels[idx / 2];
|
auto &pixel = this->pixels[idx / 2];
|
||||||
if (bpp == 4) {
|
if (pBpp == 4) {
|
||||||
if (idx & 1) {
|
if (idx & 1) {
|
||||||
pixel = (pixel & 0b0000'1111) | (palIdx << 4);
|
pixel = (pixel & 0b0000'1111) | (palIdx << 4);
|
||||||
} else {
|
} else {
|
||||||
@ -213,13 +237,13 @@ struct TileSheet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr void setPixel(int8_t bpp, const geo::Point &pt, uint8_t palIdx) noexcept {
|
constexpr void setPixel(int8_t pBpp, const geo::Point &pt, uint8_t palIdx) noexcept {
|
||||||
const auto idx = ptToIdx(pt, columns);
|
const auto idx = ptToIdx(pt, columns);
|
||||||
setPixel(bpp, idx, palIdx);
|
setPixel(pBpp, idx, palIdx);
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr auto setPixelCount(int8_t bpp, std::size_t cnt) noexcept {
|
constexpr auto setPixelCount(int8_t pBpp, std::size_t cnt) noexcept {
|
||||||
switch (bpp) {
|
switch (pBpp) {
|
||||||
case 4:
|
case 4:
|
||||||
pixels.resize(cnt / 2);
|
pixels.resize(cnt / 2);
|
||||||
return OxError(0);
|
return OxError(0);
|
||||||
@ -227,17 +251,17 @@ struct TileSheet {
|
|||||||
pixels.resize(cnt);
|
pixels.resize(cnt);
|
||||||
return OxError(0);
|
return OxError(0);
|
||||||
default:
|
default:
|
||||||
return OxError(1, "Invalid bpp used for TileSheet::SubSheet::setPixelCount");
|
return OxError(1, "Invalid pBpp used for TileSheet::SubSheet::setPixelCount");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a count of the pixels in this sheet, and not that of its children.
|
* Gets a count of the pixels in this sheet, and not that of its children.
|
||||||
* @param bpp bits per pixel, need for knowing how to count the pixels
|
* @param pBpp bits per pixel, need for knowing how to count the pixels
|
||||||
* @return a count of the pixels in this sheet
|
* @return a count of the pixels in this sheet
|
||||||
*/
|
*/
|
||||||
constexpr auto pixelCnt(int8_t bpp) const noexcept {
|
constexpr auto pixelCnt(int8_t pBpp) const noexcept {
|
||||||
return bpp == 4 ? pixels.size() * 2 : pixels.size();
|
return pBpp == 4 ? pixels.size() * 2 : pixels.size();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -386,7 +410,7 @@ struct CompactTileSheet {
|
|||||||
static constexpr auto TypeVersion = 1;
|
static constexpr auto TypeVersion = 1;
|
||||||
int8_t bpp = 0;
|
int8_t bpp = 0;
|
||||||
ox::FileAddress defaultPalette;
|
ox::FileAddress defaultPalette;
|
||||||
ox::Vector<uint8_t> pixels;
|
ox::Vector<uint8_t> pixels = {};
|
||||||
};
|
};
|
||||||
|
|
||||||
oxModelBegin(NostalgiaPalette)
|
oxModelBegin(NostalgiaPalette)
|
||||||
|
@ -22,6 +22,7 @@ target_link_libraries(
|
|||||||
NostalgiaStudio
|
NostalgiaStudio
|
||||||
NostalgiaCore
|
NostalgiaCore
|
||||||
NostalgiaGlUtils
|
NostalgiaGlUtils
|
||||||
|
lodepng
|
||||||
)
|
)
|
||||||
|
|
||||||
#target_compile_definitions(NostalgiaCore-Studio PRIVATE QT_QML_DEBUG)
|
#target_compile_definitions(NostalgiaCore-Studio PRIVATE QT_QML_DEBUG)
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
|
#include <lodepng.h>
|
||||||
|
|
||||||
#include <nostalgia/core/media.hpp>
|
#include <nostalgia/core/media.hpp>
|
||||||
#include <nostalgia/geo/point.hpp>
|
#include <nostalgia/geo/point.hpp>
|
||||||
@ -11,10 +12,29 @@
|
|||||||
|
|
||||||
namespace nostalgia::core {
|
namespace nostalgia::core {
|
||||||
|
|
||||||
|
static ox::Error toPngFile(const ox::String &path, const TileSheet::SubSheet &s, const Palette &pal, int8_t bpp) noexcept {
|
||||||
|
ox::Vector<uint8_t> pixels;
|
||||||
|
s.readPixelsTo(&pixels, bpp);
|
||||||
|
const unsigned rows = s.rows == -1 ? pixels.size() / PixelsPerTile : static_cast<unsigned>(s.rows);
|
||||||
|
const unsigned cols = s.columns == -1 ? 1 : static_cast<unsigned>(s.columns);
|
||||||
|
const auto width = cols * TileWidth;
|
||||||
|
const auto height = rows * TileHeight;
|
||||||
|
ox::Vector<unsigned char> outData(pixels.size() * 3);
|
||||||
|
for (auto idx = 0; const auto colorIdx : pixels) {
|
||||||
|
const auto pt = idxToPt(idx, static_cast<int>(cols));
|
||||||
|
const auto i = static_cast<unsigned>(pt.y * static_cast<int>(width) + pt.x) * 3;
|
||||||
|
const auto c = pal.colors[colorIdx];
|
||||||
|
outData[i + 0] = (red32(c));
|
||||||
|
outData[i + 1] = (green32(c));
|
||||||
|
outData[i + 2] = (blue32(c));
|
||||||
|
++idx;
|
||||||
|
}
|
||||||
|
return OxError(lodepng_encode24_file(path.c_str(), outData.data(), width, height));
|
||||||
|
}
|
||||||
TileSheetEditorImGui::TileSheetEditorImGui(Context *ctx, const ox::String &path): m_tileSheetEditor(ctx, path) {
|
TileSheetEditorImGui::TileSheetEditorImGui(Context *ctx, const ox::String &path): m_tileSheetEditor(ctx, path) {
|
||||||
m_ctx = ctx;
|
m_ctx = ctx;
|
||||||
m_itemPath = path;
|
m_itemPath = path;
|
||||||
const auto lastSlash = std::find(m_itemPath.rbegin(), m_itemPath.rend(), '/').offset();
|
const auto lastSlash = ox::find(m_itemPath.rbegin(), m_itemPath.rend(), '/').offset();
|
||||||
m_itemName = m_itemPath.substr(lastSlash + 1);
|
m_itemName = m_itemPath.substr(lastSlash + 1);
|
||||||
// init palette idx
|
// init palette idx
|
||||||
const auto &palPath = model()->palPath();
|
const auto &palPath = model()->palPath();
|
||||||
@ -41,6 +61,7 @@ const ox::String &TileSheetEditorImGui::itemDisplayName() const noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void TileSheetEditorImGui::exportFile() {
|
void TileSheetEditorImGui::exportFile() {
|
||||||
|
exportSubhseetToPng();
|
||||||
}
|
}
|
||||||
|
|
||||||
void TileSheetEditorImGui::cut() {
|
void TileSheetEditorImGui::cut() {
|
||||||
@ -125,9 +146,13 @@ void TileSheetEditorImGui::draw(core::Context*) noexcept {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
if (ImGui::Button("Edit", ImVec2(36, btnHeight))) {
|
if (ImGui::Button("Edit", ImVec2(51, btnHeight))) {
|
||||||
showSubsheetEditor();
|
showSubsheetEditor();
|
||||||
}
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Export", ImVec2(51, btnHeight))) {
|
||||||
|
exportSubhseetToPng();
|
||||||
|
}
|
||||||
TileSheet::SubSheetIdx path;
|
TileSheet::SubSheetIdx path;
|
||||||
static constexpr auto flags = ImGuiTableFlags_RowBg | ImGuiTableFlags_NoBordersInBody;
|
static constexpr auto flags = ImGuiTableFlags_RowBg | ImGuiTableFlags_NoBordersInBody;
|
||||||
if (ImGui::BeginTable("Subsheets", 3, flags)) {
|
if (ImGui::BeginTable("Subsheets", 3, flags)) {
|
||||||
@ -214,6 +239,21 @@ void TileSheetEditorImGui::showSubsheetEditor() noexcept {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TileSheetEditorImGui::exportSubhseetToPng() noexcept {
|
||||||
|
auto [path, err] = studio::saveFile({{"PNG", "png"}});
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// subsheet to png
|
||||||
|
const auto &img = model()->img();
|
||||||
|
const auto &s = *model()->activeSubSheet();
|
||||||
|
const auto &pal = model()->pal();
|
||||||
|
err = toPngFile(path, s, pal, img.bpp);
|
||||||
|
if (err) {
|
||||||
|
oxErrorf("Tilesheet export failed: {}", toStr(err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void TileSheetEditorImGui::drawTileSheet(const geo::Vec2 &fbSize) noexcept {
|
void TileSheetEditorImGui::drawTileSheet(const geo::Vec2 &fbSize) noexcept {
|
||||||
const auto winPos = ImGui::GetWindowPos();
|
const auto winPos = ImGui::GetWindowPos();
|
||||||
const auto fbSizei = geo::Size(static_cast<int>(fbSize.x), static_cast<int>(fbSize.y));
|
const auto fbSizei = geo::Size(static_cast<int>(fbSize.x), static_cast<int>(fbSize.y));
|
||||||
@ -230,10 +270,10 @@ void TileSheetEditorImGui::drawTileSheet(const geo::Vec2 &fbSize) noexcept {
|
|||||||
m_tileSheetEditor.draw();
|
m_tileSheetEditor.draw();
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
ImGui::Image(
|
ImGui::Image(
|
||||||
reinterpret_cast<void*>(m_framebuffer.color.id),
|
reinterpret_cast<void*>(m_framebuffer.color.id),
|
||||||
static_cast<ImVec2>(fbSize),
|
static_cast<ImVec2>(fbSize),
|
||||||
ImVec2(0, 1),
|
ImVec2(0, 1),
|
||||||
ImVec2(1, 0));
|
ImVec2(1, 0));
|
||||||
// handle input, this must come after drawing
|
// handle input, this must come after drawing
|
||||||
const auto &io = ImGui::GetIO();
|
const auto &io = ImGui::GetIO();
|
||||||
const auto mousePos = geo::Vec2(io.MousePos);
|
const auto mousePos = geo::Vec2(io.MousePos);
|
||||||
@ -242,7 +282,7 @@ void TileSheetEditorImGui::drawTileSheet(const geo::Vec2 &fbSize) noexcept {
|
|||||||
const auto wheelh = io.MouseWheelH;
|
const auto wheelh = io.MouseWheelH;
|
||||||
if (wheel != 0) {
|
if (wheel != 0) {
|
||||||
const auto zoomMod = ox::defines::OS == ox::OS::Darwin ?
|
const auto zoomMod = ox::defines::OS == ox::OS::Darwin ?
|
||||||
io.KeySuper : core::buttonDown(m_ctx, core::Key::Mod_Ctrl);
|
io.KeySuper : core::buttonDown(m_ctx, core::Key::Mod_Ctrl);
|
||||||
m_tileSheetEditor.scrollV(fbSize, wheel, zoomMod);
|
m_tileSheetEditor.scrollV(fbSize, wheel, zoomMod);
|
||||||
}
|
}
|
||||||
if (wheelh != 0) {
|
if (wheelh != 0) {
|
||||||
|
@ -43,7 +43,7 @@ class TileSheetEditorImGui: public studio::BaseEditor {
|
|||||||
};
|
};
|
||||||
std::size_t m_selectedPaletteIdx = 0;
|
std::size_t m_selectedPaletteIdx = 0;
|
||||||
Context *m_ctx = nullptr;
|
Context *m_ctx = nullptr;
|
||||||
ox::Vector<ox::String> m_paletteList;
|
ox::Vector<ox::String> m_paletteList{};
|
||||||
SubSheetEditor m_subsheetEditor;
|
SubSheetEditor m_subsheetEditor;
|
||||||
ox::String m_itemPath;
|
ox::String m_itemPath;
|
||||||
ox::String m_itemName;
|
ox::String m_itemName;
|
||||||
@ -87,6 +87,8 @@ class TileSheetEditorImGui: public studio::BaseEditor {
|
|||||||
private:
|
private:
|
||||||
void showSubsheetEditor() noexcept;
|
void showSubsheetEditor() noexcept;
|
||||||
|
|
||||||
|
void exportSubhseetToPng() noexcept;
|
||||||
|
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
constexpr auto model() const noexcept {
|
constexpr auto model() const noexcept {
|
||||||
return m_tileSheetEditor.model();
|
return m_tileSheetEditor.model();
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include <nostalgia/core/clipboard.hpp>
|
#include <nostalgia/core/clipboard.hpp>
|
||||||
#include <nostalgia/core/media.hpp>
|
#include <nostalgia/core/media.hpp>
|
||||||
|
#include <ox/std/algorithm.hpp>
|
||||||
#include <ox/std/buffer.hpp>
|
#include <ox/std/buffer.hpp>
|
||||||
#include <ox/std/memory.hpp>
|
#include <ox/std/memory.hpp>
|
||||||
|
|
||||||
@ -119,7 +120,7 @@ class DrawCommand: public TileSheetCommand {
|
|||||||
auto &subsheet = m_img->getSubSheet(m_subSheetIdx);
|
auto &subsheet = m_img->getSubSheet(m_subSheetIdx);
|
||||||
if (m_changes.back().value.idx != idx && subsheet.getPixel(m_img->bpp, idx) != m_palIdx) {
|
if (m_changes.back().value.idx != idx && subsheet.getPixel(m_img->bpp, idx) != m_palIdx) {
|
||||||
// duplicate entries are bad
|
// duplicate entries are bad
|
||||||
auto existing = std::find_if(m_changes.cbegin(), m_changes.cend(), [idx](const auto &c) {
|
auto existing = ox::find_if(m_changes.cbegin(), m_changes.cend(), [idx](const auto &c) {
|
||||||
return c.idx == idx;
|
return c.idx == idx;
|
||||||
});
|
});
|
||||||
if (existing == m_changes.cend()) {
|
if (existing == m_changes.cend()) {
|
||||||
@ -501,7 +502,7 @@ class PaletteChangeCommand: public TileSheetCommand {
|
|||||||
ox::FileAddress m_newPalette;
|
ox::FileAddress m_newPalette;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
constexpr PaletteChangeCommand(const TileSheet::SubSheetIdx &idx, TileSheet *img, const ox::String &newPalette) noexcept {
|
PaletteChangeCommand(const TileSheet::SubSheetIdx &idx, TileSheet *img, const ox::String &newPalette) noexcept {
|
||||||
m_idx = idx;
|
m_idx = idx;
|
||||||
m_img = img;
|
m_img = img;
|
||||||
m_oldPalette = m_img->defaultPalette;
|
m_oldPalette = m_img->defaultPalette;
|
||||||
|
@ -73,16 +73,19 @@ class TileSheetEditorModel: public ox::SignalHandler {
|
|||||||
|
|
||||||
void setActiveSubsheet(const TileSheet::SubSheetIdx&) noexcept;
|
void setActiveSubsheet(const TileSheet::SubSheetIdx&) noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
constexpr const TileSheet::SubSheet *activeSubSheet() const noexcept {
|
constexpr const TileSheet::SubSheet *activeSubSheet() const noexcept {
|
||||||
auto &activeSubSheet = m_img.getSubSheet(m_activeSubsSheetIdx);
|
auto &activeSubSheet = m_img.getSubSheet(m_activeSubsSheetIdx);
|
||||||
return &activeSubSheet;
|
return &activeSubSheet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
constexpr TileSheet::SubSheet *activeSubSheet() noexcept {
|
constexpr TileSheet::SubSheet *activeSubSheet() noexcept {
|
||||||
auto &activeSubSheet = m_img.getSubSheet(m_activeSubsSheetIdx);
|
auto &activeSubSheet = m_img.getSubSheet(m_activeSubsSheetIdx);
|
||||||
return &activeSubSheet;
|
return &activeSubSheet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
constexpr const TileSheet::SubSheetIdx &activeSubSheetIdx() const noexcept {
|
constexpr const TileSheet::SubSheetIdx &activeSubSheetIdx() const noexcept {
|
||||||
return m_activeSubsSheetIdx;
|
return m_activeSubsSheetIdx;
|
||||||
}
|
}
|
||||||
@ -106,6 +109,7 @@ class TileSheetEditorModel: public ox::SignalHandler {
|
|||||||
|
|
||||||
ox::Error saveFile() noexcept;
|
ox::Error saveFile() noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
constexpr studio::UndoStack *undoStack() noexcept;
|
constexpr studio::UndoStack *undoStack() noexcept;
|
||||||
|
|
||||||
bool pixelSelected(std::size_t idx) const noexcept;
|
bool pixelSelected(std::size_t idx) const noexcept;
|
||||||
|
Loading…
Reference in New Issue
Block a user