[nostalgia] Add basic support for subsheets
This commit is contained in:
parent
329ecb3266
commit
e8a046c2dc
@ -45,8 +45,8 @@ else()
|
||||
)
|
||||
endif()
|
||||
|
||||
set(
|
||||
NOSTALGIA_CORE_GENERAL_SRC
|
||||
add_library(
|
||||
NostalgiaCore-Common OBJECT
|
||||
gfx.cpp
|
||||
media.cpp
|
||||
typeconv.cpp
|
||||
@ -54,33 +54,35 @@ set(
|
||||
|
||||
add_library(
|
||||
NostalgiaCore
|
||||
${NOSTALGIA_CORE_GENERAL_SRC}
|
||||
${NOSTALGIA_CORE_IMPL_SRC}
|
||||
)
|
||||
|
||||
add_library(
|
||||
NostalgiaCore-Headless
|
||||
${NOSTALGIA_CORE_GENERAL_SRC}
|
||||
headless/core.cpp
|
||||
headless/gfx.cpp
|
||||
headless/media.cpp
|
||||
)
|
||||
|
||||
if(NOT MSVC)
|
||||
target_compile_options(NostalgiaCore PUBLIC -Wsign-conversion)
|
||||
target_compile_options(NostalgiaCore-Common PUBLIC -Wsign-conversion)
|
||||
endif()
|
||||
|
||||
target_link_libraries(
|
||||
NostalgiaCore PUBLIC
|
||||
NostalgiaCore-Common PUBLIC
|
||||
OxClaw
|
||||
OxFS
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
NostalgiaCore PUBLIC
|
||||
NostalgiaCore-Common
|
||||
${NOSTALGIA_CORE_IMPL_LIBS}
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
NostalgiaCore-Headless PUBLIC
|
||||
OxClaw
|
||||
OxFS
|
||||
NostalgiaCore-Common
|
||||
)
|
||||
|
||||
if(NOSTALGIA_BUILD_STUDIO)
|
||||
|
@ -60,30 +60,221 @@ struct TileSheet {
|
||||
struct SubSheet {
|
||||
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.TileSheet.SubSheet";
|
||||
static constexpr auto TypeVersion = 1;
|
||||
ox::BString<32> name;
|
||||
uint64_t begin = 0;
|
||||
ox::String name;
|
||||
int columns = 1;
|
||||
int rows = 1;
|
||||
ox::Vector<SubSheet> subsheets;
|
||||
ox::Vector<uint8_t> pixels;
|
||||
|
||||
constexpr SubSheet() noexcept = default;
|
||||
constexpr SubSheet(const char *pName, int pColumns, int pRows) noexcept: name(pName), columns(pColumns), rows(pRows) {
|
||||
constexpr SubSheet(const SubSheet &other) noexcept {
|
||||
name = other.name;
|
||||
columns = other.columns;
|
||||
rows = other.rows;
|
||||
subsheets = other.subsheets;
|
||||
pixels = other.pixels;
|
||||
}
|
||||
constexpr SubSheet(SubSheet &&other) noexcept {
|
||||
name = std::move(other.name);
|
||||
columns = other.columns;
|
||||
rows = other.rows;
|
||||
subsheets = std::move(other.subsheets);
|
||||
pixels = std::move(other.pixels);
|
||||
other.name = "";
|
||||
other.columns = 0;
|
||||
other.rows = 0;
|
||||
}
|
||||
constexpr SubSheet(const char *pName, int pColumns, int pRows) noexcept:
|
||||
name(pName), columns(pColumns), rows(pRows), pixels(static_cast<std::size_t>(columns * rows * PixelsPerTile)) {
|
||||
}
|
||||
constexpr SubSheet(const char *pName, int pColumns, int pRows, ox::Vector<uint8_t> pPixels) noexcept:
|
||||
name(pName), columns(pColumns), rows(pRows), pixels(std::move(pPixels)) {
|
||||
}
|
||||
|
||||
constexpr SubSheet &operator=(const SubSheet &other) noexcept {
|
||||
name = other.name;
|
||||
columns = other.columns;
|
||||
rows = other.rows;
|
||||
subsheets = other.subsheets;
|
||||
pixels = other.pixels;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr SubSheet &operator=(SubSheet &&other) noexcept {
|
||||
name = std::move(other.name);
|
||||
columns = other.columns;
|
||||
rows = other.rows;
|
||||
subsheets = std::move(other.subsheets);
|
||||
pixels = std::move(other.pixels);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads all pixels of this sheet or its children into the given pixel list
|
||||
* @param pixels
|
||||
*/
|
||||
void readPixelsTo(ox::Vector<uint8_t> *pPixels) const noexcept {
|
||||
if (subsheets.size()) {
|
||||
for (auto &s: subsheets) {
|
||||
s.readPixelsTo(pPixels);
|
||||
}
|
||||
} else {
|
||||
for (auto p : this->pixels) {
|
||||
pPixels->emplace_back(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr std::size_t size() const noexcept {
|
||||
return static_cast<std::size_t>(columns) * static_cast<std::size_t>(rows);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr std::size_t unusedPixels() const noexcept {
|
||||
std::size_t childrenSize = 0;
|
||||
for (auto &c : subsheets) {
|
||||
childrenSize += c.size();
|
||||
}
|
||||
return size() - childrenSize;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr uint8_t getPixel4Bpp(std::size_t idx) const noexcept {
|
||||
if (idx & 1) {
|
||||
return this->pixels[idx / 2] >> 4;
|
||||
} else {
|
||||
return this->pixels[idx / 2] & 0b0000'1111;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr uint8_t getPixel8Bpp(std::size_t idx) const noexcept {
|
||||
return this->pixels[idx];
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr auto getPixel(int8_t bpp, std::size_t idx) const noexcept {
|
||||
if (bpp == 4) {
|
||||
return getPixel4Bpp(idx);
|
||||
} else {
|
||||
return getPixel8Bpp(idx);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr auto getPixel4Bpp(const geo::Point &pt) const noexcept {
|
||||
const auto idx = ptToIdx(pt, columns);
|
||||
return getPixel4Bpp(idx);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr auto getPixel8Bpp(const geo::Point &pt) const noexcept {
|
||||
const auto idx = ptToIdx(pt, columns);
|
||||
return getPixel8Bpp(idx);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr auto getPixel(int8_t bpp, const geo::Point &pt) const noexcept {
|
||||
const auto idx = ptToIdx(pt, columns);
|
||||
return getPixel(bpp, idx);
|
||||
}
|
||||
|
||||
constexpr void setPixel(int8_t bpp, uint64_t idx, uint8_t palIdx) noexcept {
|
||||
auto &pixel = this->pixels[idx / 2];
|
||||
if (bpp == 4) {
|
||||
if (idx & 1) {
|
||||
pixel = (pixel & 0b0000'1111) | (palIdx << 4);
|
||||
} else {
|
||||
pixel = (pixel & 0b1111'0000) | (palIdx);
|
||||
}
|
||||
} else {
|
||||
pixel = palIdx;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr void setPixel(int8_t bpp, const geo::Point &pt, uint8_t palIdx) noexcept {
|
||||
const auto idx = ptToIdx(pt, columns);
|
||||
setPixel(bpp, idx, palIdx);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @return a count of the pixels in this sheet
|
||||
*/
|
||||
constexpr auto pixelCnt(int8_t bpp) const noexcept {
|
||||
return bpp == 4 ? pixels.size() * 2 : pixels.size();
|
||||
}
|
||||
};
|
||||
|
||||
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.TileSheet";
|
||||
static constexpr auto TypeVersion = 1;
|
||||
static constexpr auto TypeVersion = 2;
|
||||
int8_t bpp = 0;
|
||||
ox::FileAddress defaultPalette;
|
||||
ox::Vector<uint8_t> pixels;
|
||||
SubSheet subsheet;
|
||||
|
||||
constexpr TileSheet() noexcept = default;
|
||||
inline TileSheet(const TileSheet &other) noexcept {
|
||||
bpp = other.bpp;
|
||||
defaultPalette = other.defaultPalette;
|
||||
subsheet = other.subsheet;
|
||||
}
|
||||
inline TileSheet(TileSheet &&other) noexcept {
|
||||
bpp = other.bpp;
|
||||
defaultPalette = std::move(other.defaultPalette);
|
||||
subsheet = std::move(other.subsheet);
|
||||
}
|
||||
|
||||
inline auto &operator=(const TileSheet &other) noexcept {
|
||||
bpp = other.bpp;
|
||||
defaultPalette = other.defaultPalette;
|
||||
subsheet = other.subsheet;
|
||||
return *this;
|
||||
}
|
||||
inline auto &operator=(TileSheet &&other) noexcept {
|
||||
bpp = other.bpp;
|
||||
defaultPalette = std::move(other.defaultPalette);
|
||||
subsheet = std::move(other.subsheet);
|
||||
return *this;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr auto validateSubSheetIdx(const SubSheetIdx &pIdx, std::size_t pIdxIt, const SubSheet *pSubsheet) noexcept {
|
||||
if (pIdxIt == pIdx.size()) {
|
||||
return pIdx;
|
||||
}
|
||||
const auto currentIdx = pIdx[pIdxIt];
|
||||
if (pSubsheet->subsheets.size() <= currentIdx) {
|
||||
auto out = pIdx;
|
||||
out.back().value = pSubsheet->subsheets.size() - 1;
|
||||
return out;
|
||||
}
|
||||
return validateSubSheetIdx(pIdx, pIdxIt + 1, &pSubsheet->subsheets[pIdx[pIdxIt]]);
|
||||
}
|
||||
|
||||
/**
|
||||
* validateSubSheetIdx takes a SubSheetIdx and moves the index to the
|
||||
* preceding or parent sheet if the current corresponding sheet does
|
||||
* not exist.
|
||||
* @param idx SubSheetIdx to validate and correct
|
||||
* @return a valid version of idx
|
||||
*/
|
||||
[[nodiscard]]
|
||||
constexpr auto validateSubSheetIdx(const SubSheetIdx &idx) noexcept {
|
||||
return validateSubSheetIdx(idx, 0, &subsheet);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr static const auto &getSubSheet(const SubSheetIdx &idx, std::size_t idxIt, const SubSheet *pSubsheet) noexcept {
|
||||
if (idxIt == idx.size()) {
|
||||
return *pSubsheet;
|
||||
}
|
||||
return getSubSheet(idx, idxIt + 1, &pSubsheet->subsheets[idx[idxIt]]);
|
||||
const auto currentIdx = idx[idxIt];
|
||||
if (pSubsheet->subsheets.size() < currentIdx) {
|
||||
return *pSubsheet;
|
||||
}
|
||||
return getSubSheet(idx, idxIt + 1, &pSubsheet->subsheets[currentIdx]);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
@ -104,6 +295,17 @@ struct TileSheet {
|
||||
return getSubSheet(idx, 0, &subsheet);
|
||||
}
|
||||
|
||||
constexpr ox::Error addSubSheet(const SubSheetIdx &idx) noexcept {
|
||||
auto &parent = getSubSheet(idx);
|
||||
if (parent.subsheets.size() < 2) {
|
||||
parent.subsheets.emplace_back(ox::sfmt("Subsheet {}", parent.subsheets.size()).c_str(), 1, 1);
|
||||
} else {
|
||||
parent.subsheets.emplace_back("Subsheet 0", parent.columns, parent.rows);
|
||||
parent.subsheets.emplace_back("Subsheet 1", 1, 1);
|
||||
}
|
||||
return OxError(0);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr static auto rmSubSheet(const SubSheetIdx &idx, std::size_t idxIt, SubSheet *pSubsheet) noexcept {
|
||||
if (idxIt == idx.size() - 1) {
|
||||
@ -117,88 +319,29 @@ struct TileSheet {
|
||||
return rmSubSheet(idx, 0, &subsheet);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr const auto &columns(const SubSheetIdx &idx = {}) const noexcept {
|
||||
return getSubSheet(idx).columns;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr const auto &rows(const SubSheetIdx &idx = {}) const noexcept {
|
||||
return getSubSheet(idx).rows;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr auto &columns(const SubSheetIdx &idx = {}) noexcept {
|
||||
return getSubSheet(idx).columns;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr auto &rows(const SubSheetIdx &idx = {}) noexcept {
|
||||
return getSubSheet(idx).rows;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr uint8_t getPixel4Bpp(std::size_t idx) const noexcept {
|
||||
oxAssert(bpp == 4, "TileSheetV1::getPixel4Bpp: wrong bpp");
|
||||
if (idx & 1) {
|
||||
return this->pixels[idx / 2] >> 4;
|
||||
} else {
|
||||
return this->pixels[idx / 2] & 0b0000'1111;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr uint8_t getPixel8Bpp(std::size_t idx) const noexcept {
|
||||
oxAssert(bpp == 8, "TileSheetV1::getPixel8Bpp: wrong bpp");
|
||||
return this->pixels[idx];
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr auto getPixel(std::size_t idx) const noexcept {
|
||||
if (this->bpp == 4) {
|
||||
return getPixel4Bpp(idx);
|
||||
} else {
|
||||
return getPixel8Bpp(idx);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr auto getPixel4Bpp(const geo::Point &pt, const SubSheetIdx &subsheetIdx) const noexcept {
|
||||
oxAssert(bpp == 4, "TileSheetV1::getPixel4Bpp: wrong bpp");
|
||||
const auto idx = ptToIdx(pt, this->getSubSheet(subsheetIdx).columns);
|
||||
return getPixel4Bpp(idx);
|
||||
auto &s = this->getSubSheet(subsheetIdx);
|
||||
const auto idx = ptToIdx(pt, s.columns);
|
||||
return s.getPixel4Bpp(idx);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr auto getPixel8Bpp(const geo::Point &pt, const SubSheetIdx &subsheetIdx) const noexcept {
|
||||
oxAssert(bpp == 8, "TileSheetV1::getPixel8Bpp: wrong bpp");
|
||||
const auto idx = ptToIdx(pt, this->getSubSheet(subsheetIdx).columns);
|
||||
return getPixel8Bpp(idx);
|
||||
auto &s = this->getSubSheet(subsheetIdx);
|
||||
const auto idx = ptToIdx(pt, s.columns);
|
||||
return s.getPixel8Bpp(idx);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr auto getPixel(const geo::Point &pt, const SubSheetIdx &subsheetIdx) const noexcept {
|
||||
const auto idx = ptToIdx(pt, this->getSubSheet(subsheetIdx).columns);
|
||||
return getPixel(idx);
|
||||
auto pixels() const noexcept {
|
||||
ox::Vector<uint8_t> out;
|
||||
subsheet.readPixelsTo(&out);
|
||||
return out;
|
||||
}
|
||||
|
||||
constexpr void setPixel(uint64_t idx, uint8_t palIdx) noexcept {
|
||||
auto &pixel = this->pixels[idx / 2];
|
||||
if (bpp == 4) {
|
||||
if (idx & 1) {
|
||||
pixel = (pixel & 0b0000'1111) | (palIdx << 4);
|
||||
} else {
|
||||
pixel = (pixel & 0b1111'0000) | (palIdx);
|
||||
}
|
||||
} else {
|
||||
pixel = palIdx;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr void setPixel(const SubSheetIdx &subsheetIdx, const geo::Point &pt, uint8_t palIdx) noexcept {
|
||||
const auto idx = ptToIdx(pt, this->getSubSheet(subsheetIdx).columns);
|
||||
setPixel(idx, palIdx);
|
||||
}
|
||||
};
|
||||
|
||||
struct CompactTileSheet {
|
||||
@ -228,16 +371,15 @@ oxModelEnd()
|
||||
|
||||
oxModelBegin(TileSheet::SubSheet)
|
||||
oxModelField(name);
|
||||
oxModelField(begin);
|
||||
oxModelField(rows);
|
||||
oxModelField(columns);
|
||||
oxModelField(subsheets)
|
||||
oxModelField(pixels)
|
||||
oxModelEnd()
|
||||
|
||||
oxModelBegin(TileSheet)
|
||||
oxModelField(bpp)
|
||||
oxModelField(defaultPalette)
|
||||
oxModelField(pixels)
|
||||
oxModelField(subsheet)
|
||||
oxModelEnd()
|
||||
|
||||
|
@ -6,7 +6,7 @@ add_library(
|
||||
#newpalettewizard.cpp
|
||||
#paletteeditor.cpp
|
||||
tilesheeteditor-imgui.cpp
|
||||
tilesheeteditor.cpp
|
||||
tilesheeteditorview.cpp
|
||||
tilesheeteditormodel.cpp
|
||||
tilesheetpixelgrid.cpp
|
||||
tilesheetpixels.cpp
|
||||
|
@ -2,8 +2,6 @@
|
||||
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include <nostalgia/core/media.hpp>
|
||||
@ -32,37 +30,114 @@ void TileSheetEditorImGui::exportFile() {
|
||||
}
|
||||
|
||||
void TileSheetEditorImGui::cut() {
|
||||
m_tileSheetEditor.cut();
|
||||
model()->cut();
|
||||
}
|
||||
|
||||
void TileSheetEditorImGui::copy() {
|
||||
m_tileSheetEditor.copy();
|
||||
model()->copy();
|
||||
}
|
||||
|
||||
void TileSheetEditorImGui::paste() {
|
||||
m_tileSheetEditor.paste();
|
||||
model()->paste();
|
||||
}
|
||||
|
||||
void TileSheetEditorImGui::draw(core::Context*) noexcept {
|
||||
const auto paneSize = ImGui::GetContentRegionAvail();
|
||||
const auto tileSheetParentSize = ImVec2(paneSize.x - m_palViewWidth, paneSize.y);
|
||||
const auto fbSize = geo::Vec2(tileSheetParentSize.x - 16, tileSheetParentSize.y - 16);
|
||||
ImGui::BeginChild("child1", tileSheetParentSize, true);
|
||||
drawTileSheet(fbSize);
|
||||
ImGui::BeginChild("TileSheetView", tileSheetParentSize, true);
|
||||
{
|
||||
drawTileSheet(fbSize);
|
||||
}
|
||||
ImGui::EndChild();
|
||||
ImGui::SameLine();
|
||||
// draw palette/color picker
|
||||
ImGui::BeginChild("child2", ImVec2(m_palViewWidth - 8, paneSize.y), true);
|
||||
drawPalettePicker();
|
||||
ImGui::BeginChild("Controls", ImVec2(m_palViewWidth - 8, paneSize.y), true);
|
||||
{
|
||||
// draw palette/color picker
|
||||
ImGui::BeginChild("child1", ImVec2(m_palViewWidth - 24, paneSize.y / 2.07f), true);
|
||||
{
|
||||
drawPalettePicker();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
ImGui::BeginChild("child2", ImVec2(m_palViewWidth - 24, paneSize.y / 2.07f), true);
|
||||
{
|
||||
const auto btnSize = ImVec2(18, 18);
|
||||
if (ImGui::Button("+", btnSize)) {
|
||||
auto insertOnIdx = model()->activeSubSheetIdx();
|
||||
const auto &parent = *model()->activeSubSheet();
|
||||
model()->addSubsheet(insertOnIdx);
|
||||
insertOnIdx.emplace_back(parent.subsheets.size() - 1);
|
||||
model()->setActiveSubsheet(insertOnIdx);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("-", btnSize)) {
|
||||
const auto &activeSubsheetIdx = model()->activeSubSheetIdx();
|
||||
if (activeSubsheetIdx.size() > 0) {
|
||||
model()->rmSubsheet(activeSubsheetIdx);
|
||||
}
|
||||
}
|
||||
TileSheet::SubSheetIdx path;
|
||||
constexpr auto flags = ImGuiTableFlags_RowBg | ImGuiTableFlags_NoBordersInBody;
|
||||
if (ImGui::BeginTable("Subsheets", 3, flags)) {
|
||||
ImGui::TableSetupColumn("Subsheet", ImGuiTableColumnFlags_NoHide);
|
||||
ImGui::TableSetupColumn("Columns", ImGuiTableColumnFlags_WidthFixed, 50);
|
||||
ImGui::TableSetupColumn("Rows", ImGuiTableColumnFlags_WidthFixed, 50);
|
||||
ImGui::TableHeadersRow();
|
||||
drawSubsheetSelector(&m_tileSheetEditor.img().subsheet, &path);
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
void TileSheetEditorImGui::drawSubsheetSelector(TileSheet::SubSheet *subsheet, TileSheet::SubSheetIdx *path) noexcept {
|
||||
ImGui::TableNextRow(0, 5);
|
||||
using Str = ox::BasicString<100>;
|
||||
auto pathStr = ox::join<Str>("##", *path).value;
|
||||
auto lbl = ox::sfmt<Str>("{}##{}", subsheet->name, pathStr);
|
||||
const auto rowSelected = *path == model()->activeSubSheetIdx();
|
||||
const auto flags = ImGuiTreeNodeFlags_SpanFullWidth
|
||||
| ImGuiTreeNodeFlags_OpenOnArrow
|
||||
| ImGuiTreeNodeFlags_DefaultOpen
|
||||
| (subsheet->subsheets.size() ? 0 : ImGuiTreeNodeFlags_Leaf)
|
||||
| (rowSelected ? ImGuiTreeNodeFlags_Selected : 0);
|
||||
ImGui::TableNextColumn();
|
||||
const auto open = ImGui::TreeNodeEx(lbl.c_str(), flags);
|
||||
if (ImGui::IsItemClicked()) {
|
||||
model()->setActiveSubsheet(*path);
|
||||
}
|
||||
if (subsheet->subsheets.size()) {
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("--");
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("--");
|
||||
} else {
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%d", subsheet->columns);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%d", subsheet->rows);
|
||||
}
|
||||
if (open) {
|
||||
for (auto i = 0ul; auto &child : subsheet->subsheets) {
|
||||
path->push_back(i);
|
||||
ImGui::PushID(static_cast<int>(i));
|
||||
drawSubsheetSelector(&child, path);
|
||||
ImGui::PopID();
|
||||
path->pop_back();
|
||||
++i;
|
||||
}
|
||||
ImGui::TreePop();
|
||||
}
|
||||
}
|
||||
|
||||
studio::UndoStack *TileSheetEditorImGui::undoStack() noexcept {
|
||||
return m_tileSheetEditor.undoStack();
|
||||
return model()->undoStack();
|
||||
}
|
||||
|
||||
void TileSheetEditorImGui::saveItem() {
|
||||
const auto err = m_tileSheetEditor.model()->saveFile();
|
||||
const auto err = model()->saveFile();
|
||||
if (!err) {
|
||||
this->setUnsavedChanges(false);
|
||||
} else {
|
||||
@ -143,8 +218,7 @@ void TileSheetEditorImGui::drawPalettePicker() noexcept {
|
||||
}
|
||||
}
|
||||
|
||||
ox::Error TileSheetEditorImGui::markUnsavedChanges() noexcept {
|
||||
oxDebug("markUnsavedChanges");
|
||||
ox::Error TileSheetEditorImGui::markUnsavedChanges(int) noexcept {
|
||||
setUnsavedChanges(true);
|
||||
return OxError(0);
|
||||
}
|
||||
|
@ -14,7 +14,7 @@
|
||||
|
||||
#include "tilesheetpixelgrid.hpp"
|
||||
#include "tilesheetpixels.hpp"
|
||||
#include "tilesheeteditor.hpp"
|
||||
#include "tilesheeteditorview.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
@ -24,8 +24,8 @@ class TileSheetEditorImGui: public studio::Editor {
|
||||
ox::String m_itemPath;
|
||||
ox::String m_itemName;
|
||||
glutils::FrameBuffer m_framebuffer;
|
||||
TileSheetEditor m_tileSheetEditor;
|
||||
float m_palViewWidth = 200;
|
||||
TileSheetEditorView m_tileSheetEditor;
|
||||
float m_palViewWidth = 300;
|
||||
geo::Vec2 m_prevMouseDownPos;
|
||||
|
||||
public:
|
||||
@ -47,12 +47,24 @@ class TileSheetEditorImGui: public studio::Editor {
|
||||
|
||||
void draw(core::Context*) noexcept override;
|
||||
|
||||
void drawSubsheetSelector(TileSheet::SubSheet*, TileSheet::SubSheetIdx *path) noexcept;
|
||||
|
||||
studio::UndoStack *undoStack() noexcept final;
|
||||
|
||||
protected:
|
||||
void saveItem() override;
|
||||
|
||||
private:
|
||||
[[nodiscard]]
|
||||
constexpr auto model() const noexcept {
|
||||
return m_tileSheetEditor.model();
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr auto model() noexcept {
|
||||
return m_tileSheetEditor.model();
|
||||
}
|
||||
|
||||
void setPalette();
|
||||
|
||||
void saveState();
|
||||
@ -73,7 +85,7 @@ class TileSheetEditorImGui: public studio::Editor {
|
||||
private:
|
||||
ox::Error updateAfterClicked() noexcept;
|
||||
|
||||
ox::Error markUnsavedChanges() noexcept;
|
||||
ox::Error markUnsavedChanges(int) noexcept;
|
||||
|
||||
};
|
||||
|
||||
|
@ -29,12 +29,16 @@ void TileSheetEditorModel::paste() {
|
||||
}
|
||||
|
||||
void TileSheetEditorModel::drawCommand(const geo::Point &pt, std::size_t palIdx) noexcept {
|
||||
auto &activeSubSheet = m_img.getSubSheet(m_activeSubsSheetIdx);
|
||||
const auto idx = ptToIdx(pt, activeSubSheet.columns);
|
||||
if (idx >= activeSubSheet.pixelCnt(m_img.bpp)) {
|
||||
return;
|
||||
}
|
||||
if (m_ongoingDrawCommand) {
|
||||
m_updated = m_updated || m_ongoingDrawCommand->append(ptToIdx(pt, m_img.columns()));
|
||||
m_updated = m_updated || m_ongoingDrawCommand->append(idx);
|
||||
} else {
|
||||
const auto idx = ptToIdx(pt, m_img.columns());
|
||||
if (m_img.getPixel(idx) != palIdx) {
|
||||
pushCommand(new DrawCommand(&m_img, idx, palIdx));
|
||||
if (activeSubSheet.getPixel(m_img.bpp, idx) != palIdx) {
|
||||
pushCommand(new DrawCommand(&m_img, m_activeSubsSheetIdx, idx, palIdx));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -43,12 +47,32 @@ void TileSheetEditorModel::endDrawCommand() noexcept {
|
||||
m_ongoingDrawCommand = nullptr;
|
||||
}
|
||||
|
||||
void TileSheetEditorModel::addSubsheet(const TileSheet::SubSheetIdx &parentIdx) noexcept {
|
||||
pushCommand(new AddSubSheetCommand(&m_img, parentIdx));
|
||||
}
|
||||
|
||||
void TileSheetEditorModel::rmSubsheet(const TileSheet::SubSheetIdx &idx) noexcept {
|
||||
pushCommand(new RmSubSheetCommand(&m_img, idx));
|
||||
}
|
||||
|
||||
void TileSheetEditorModel::setActiveSubsheet(const TileSheet::SubSheetIdx &idx) noexcept {
|
||||
m_activeSubsSheetIdx = idx;
|
||||
this->activeSubsheetChanged.emit(m_activeSubsSheetIdx);
|
||||
}
|
||||
|
||||
bool TileSheetEditorModel::updated() const noexcept {
|
||||
return m_updated;
|
||||
}
|
||||
|
||||
ox::Error TileSheetEditorModel::markUpdated() noexcept {
|
||||
ox::Error TileSheetEditorModel::markUpdated(int cmdId) noexcept {
|
||||
m_updated = true;
|
||||
if (cmdId == CommandId::AddSubSheet || cmdId == CommandId::RmSubSheet) {
|
||||
// make sure the current active SubSheet is still valid
|
||||
auto idx = m_img.validateSubSheetIdx(m_activeSubsSheetIdx);
|
||||
if (idx != m_activeSubsSheetIdx) {
|
||||
setActiveSubsheet(idx);
|
||||
}
|
||||
}
|
||||
return OxError(0);
|
||||
}
|
||||
|
||||
@ -63,8 +87,9 @@ ox::Error TileSheetEditorModel::saveFile() noexcept {
|
||||
}
|
||||
|
||||
void TileSheetEditorModel::getFillPixels(bool *pixels, const geo::Point &pt, int oldColor) const noexcept {
|
||||
const auto tileIdx = [this](const geo::Point &pt) noexcept {
|
||||
return ptToIdx(pt, img().columns()) / PixelsPerTile;
|
||||
auto &activeSubSheet = *this->activeSubSheet();
|
||||
const auto tileIdx = [activeSubSheet](const geo::Point &pt) noexcept {
|
||||
return ptToIdx(pt, activeSubSheet.columns) / PixelsPerTile;
|
||||
};
|
||||
// get points
|
||||
const auto leftPt = pt + geo::Point(-1, 0);
|
||||
@ -72,24 +97,24 @@ void TileSheetEditorModel::getFillPixels(bool *pixels, const geo::Point &pt, int
|
||||
const auto topPt = pt + geo::Point(0, -1);
|
||||
const auto bottomPt = pt + geo::Point(0, 1);
|
||||
// calculate indices
|
||||
const auto idx = ptToIdx(pt, m_img.columns());
|
||||
const auto leftIdx = ptToIdx(leftPt, m_img.columns());
|
||||
const auto rightIdx = ptToIdx(rightPt, m_img.columns());
|
||||
const auto topIdx = ptToIdx(topPt, m_img.columns());
|
||||
const auto bottomIdx = ptToIdx(bottomPt, m_img.columns());
|
||||
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) && m_img.pixels[leftIdx] == oldColor) {
|
||||
if (!pixels[leftIdx % PixelsPerTile] && tile == tileIdx(leftPt) && activeSubSheet.pixels[leftIdx] == oldColor) {
|
||||
getFillPixels(pixels, leftPt, oldColor);
|
||||
}
|
||||
if (!pixels[rightIdx % PixelsPerTile] && tile == tileIdx(rightPt) && m_img.pixels[rightIdx] == oldColor) {
|
||||
if (!pixels[rightIdx % PixelsPerTile] && tile == tileIdx(rightPt) && activeSubSheet.pixels[rightIdx] == oldColor) {
|
||||
getFillPixels(pixels, rightPt, oldColor);
|
||||
}
|
||||
if (!pixels[topIdx % PixelsPerTile] && tile == tileIdx(topPt) && m_img.pixels[topIdx] == oldColor) {
|
||||
if (!pixels[topIdx % PixelsPerTile] && tile == tileIdx(topPt) && activeSubSheet.pixels[topIdx] == oldColor) {
|
||||
getFillPixels(pixels, topPt, oldColor);
|
||||
}
|
||||
if (!pixels[bottomIdx % PixelsPerTile] && tile == tileIdx(bottomPt) && m_img.pixels[bottomIdx] == oldColor) {
|
||||
if (!pixels[bottomIdx % PixelsPerTile] && tile == tileIdx(bottomPt) && activeSubSheet.pixels[bottomIdx] == oldColor) {
|
||||
getFillPixels(pixels, bottomPt, oldColor);
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/std/trace.hpp>
|
||||
#include <ox/std/string.hpp>
|
||||
|
||||
#include <nostalgia/core/gfx.hpp>
|
||||
@ -15,13 +16,19 @@ namespace nostalgia::core {
|
||||
|
||||
// Command IDs to use with QUndoCommand::id()
|
||||
enum class CommandId {
|
||||
UpdatePixel = 1,
|
||||
ModPixel = 2,
|
||||
UpdateDimension = 3,
|
||||
InsertTile = 4,
|
||||
ClipboardPaste = 5,
|
||||
Draw = 1,
|
||||
AddSubSheet = 2,
|
||||
RmSubSheet = 3,
|
||||
};
|
||||
|
||||
constexpr bool operator==(CommandId c, int i) noexcept {
|
||||
return static_cast<int>(c) == i;
|
||||
}
|
||||
|
||||
constexpr bool operator==(int i, CommandId c) noexcept {
|
||||
return static_cast<int>(c) == i;
|
||||
}
|
||||
|
||||
struct DrawCommand: public studio::UndoCommand {
|
||||
private:
|
||||
struct Change {
|
||||
@ -29,25 +36,29 @@ struct DrawCommand: public studio::UndoCommand {
|
||||
uint16_t oldPalIdx = 0;
|
||||
};
|
||||
TileSheet *m_img = nullptr;
|
||||
TileSheet::SubSheetIdx m_subSheetIdx;
|
||||
ox::Vector<Change, 8> m_changes;
|
||||
int m_palIdx = 0;
|
||||
|
||||
public:
|
||||
constexpr DrawCommand(TileSheet *img, std::size_t idx, int palIdx) noexcept {
|
||||
constexpr DrawCommand(TileSheet *img, const TileSheet::SubSheetIdx &subSheetIdx, std::size_t idx, int palIdx) noexcept {
|
||||
m_img = img;
|
||||
m_changes.emplace_back(idx, m_img->getPixel(idx));
|
||||
auto &subsheet = m_img->getSubSheet(subSheetIdx);
|
||||
m_subSheetIdx = subSheetIdx;
|
||||
m_changes.emplace_back(idx, subsheet.getPixel(m_img->bpp, idx));
|
||||
m_palIdx = palIdx;
|
||||
}
|
||||
|
||||
constexpr auto append(std::size_t idx) noexcept {
|
||||
if (m_changes.back().value.idx != idx && m_img->getPixel(idx) != m_palIdx) {
|
||||
auto &subsheet = m_img->getSubSheet(m_subSheetIdx);
|
||||
if (m_changes.back().value.idx != idx && subsheet.getPixel(m_img->bpp, idx) != m_palIdx) {
|
||||
// duplicate entries are bad
|
||||
auto existing = std::find_if(m_changes.cbegin(), m_changes.cend(), [idx](const auto &c) {
|
||||
return c.idx == idx;
|
||||
});
|
||||
if (existing == m_changes.cend()) {
|
||||
m_changes.emplace_back(idx, m_img->getPixel(idx));
|
||||
m_img->setPixel(idx, m_palIdx);
|
||||
m_changes.emplace_back(idx, subsheet.getPixel(m_img->bpp, idx));
|
||||
subsheet.setPixel(m_img->bpp, idx, m_palIdx);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -55,17 +66,118 @@ struct DrawCommand: public studio::UndoCommand {
|
||||
}
|
||||
|
||||
void redo() noexcept final {
|
||||
auto &subsheet = m_img->getSubSheet(m_subSheetIdx);
|
||||
for (const auto &c : m_changes) {
|
||||
m_img->setPixel(c.idx, m_palIdx);
|
||||
subsheet.setPixel(m_img->bpp, c.idx, m_palIdx);
|
||||
}
|
||||
}
|
||||
|
||||
void undo() noexcept final {
|
||||
auto &subsheet = m_img->getSubSheet(m_subSheetIdx);
|
||||
for (const auto &c : m_changes) {
|
||||
m_img->setPixel(c.idx, c.oldPalIdx);
|
||||
subsheet.setPixel(m_img->bpp, c.idx, c.oldPalIdx);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
int commandId() const noexcept final {
|
||||
return static_cast<int>(CommandId::Draw);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
struct AddSubSheetCommand: public studio::UndoCommand {
|
||||
private:
|
||||
TileSheet *m_img = nullptr;
|
||||
TileSheet::SubSheetIdx m_parentIdx;
|
||||
ox::Vector<TileSheet::SubSheetIdx, 2> m_addedSheets;
|
||||
|
||||
public:
|
||||
constexpr AddSubSheetCommand(TileSheet *img, const TileSheet::SubSheetIdx &parentIdx) noexcept {
|
||||
m_img = img;
|
||||
m_parentIdx = parentIdx;
|
||||
auto &parent = m_img->getSubSheet(m_parentIdx);
|
||||
if (parent.subsheets.size()) {
|
||||
auto idx = m_parentIdx;
|
||||
idx.emplace_back(parent.subsheets.size());
|
||||
m_addedSheets.push_back(idx);
|
||||
} else {
|
||||
auto idx = m_parentIdx;
|
||||
idx.emplace_back(0);
|
||||
m_addedSheets.push_back(idx);
|
||||
idx.back().value = 1;
|
||||
m_addedSheets.push_back(idx);
|
||||
}
|
||||
}
|
||||
|
||||
void redo() noexcept final {
|
||||
auto &parent = m_img->getSubSheet(m_parentIdx);
|
||||
if (m_addedSheets.size() < 2) {
|
||||
auto i = parent.subsheets.size();
|
||||
parent.subsheets.emplace_back(ox::sfmt("Subsheet {}", i).c_str(), 1, 1);
|
||||
} else {
|
||||
parent.subsheets.emplace_back("Subsheet 0", parent.columns, parent.rows, std::move(parent.pixels));
|
||||
parent.rows = 0;
|
||||
parent.columns = 0;
|
||||
parent.subsheets.emplace_back("Subsheet 1", 1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
void undo() noexcept final {
|
||||
auto &parent = m_img->getSubSheet(m_parentIdx);
|
||||
if (parent.subsheets.size() == 2) {
|
||||
auto s = parent.subsheets[0];
|
||||
parent.rows = s.rows;
|
||||
parent.columns = s.columns;
|
||||
parent.pixels = std::move(s.pixels);
|
||||
parent.subsheets.clear();
|
||||
} else {
|
||||
for (auto idx = m_addedSheets.rbegin(); idx != m_addedSheets.rend(); ++idx) {
|
||||
oxLogError(m_img->rmSubSheet(*idx));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
int commandId() const noexcept final {
|
||||
return static_cast<int>(CommandId::AddSubSheet);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
struct RmSubSheetCommand: public studio::UndoCommand {
|
||||
private:
|
||||
TileSheet *m_img = nullptr;
|
||||
TileSheet::SubSheetIdx m_idx;
|
||||
TileSheet::SubSheetIdx m_parentIdx;
|
||||
TileSheet::SubSheet m_sheet;
|
||||
|
||||
public:
|
||||
constexpr RmSubSheetCommand(TileSheet *img, const TileSheet::SubSheetIdx &idx) noexcept {
|
||||
m_img = img;
|
||||
m_idx = idx;
|
||||
m_parentIdx = idx;
|
||||
m_parentIdx.pop_back();
|
||||
auto &parent = m_img->getSubSheet(m_parentIdx);
|
||||
m_sheet = parent.subsheets[idx.back().value];
|
||||
}
|
||||
|
||||
void redo() noexcept final {
|
||||
auto &parent = m_img->getSubSheet(m_parentIdx);
|
||||
oxLogError(parent.subsheets.erase(m_idx.back().value).error);
|
||||
}
|
||||
|
||||
void undo() noexcept final {
|
||||
auto &parent = m_img->getSubSheet(m_parentIdx);
|
||||
auto i = m_idx.back().value;
|
||||
parent.subsheets.insert(i, m_sheet);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
int commandId() const noexcept final {
|
||||
return static_cast<int>(CommandId::RmSubSheet);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
struct TileSheetClipboard {
|
||||
@ -109,8 +221,12 @@ oxModelEnd()
|
||||
|
||||
class TileSheetEditorModel: public ox::SignalHandler {
|
||||
|
||||
public:
|
||||
ox::Signal<ox::Error(const TileSheet::SubSheetIdx&)> activeSubsheetChanged;
|
||||
|
||||
private:
|
||||
TileSheet m_img;
|
||||
TileSheet::SubSheetIdx m_activeSubsSheetIdx;
|
||||
AssetRef<Palette> m_pal;
|
||||
studio::UndoStack m_undoStack;
|
||||
DrawCommand *m_ongoingDrawCommand = nullptr;
|
||||
@ -142,18 +258,36 @@ class TileSheetEditorModel: public ox::SignalHandler {
|
||||
|
||||
void endDrawCommand() noexcept;
|
||||
|
||||
void addSubsheet(const TileSheet::SubSheetIdx &parentIdx) noexcept;
|
||||
|
||||
void rmSubsheet(const TileSheet::SubSheetIdx &idx) noexcept;
|
||||
|
||||
void setActiveSubsheet(const TileSheet::SubSheetIdx&) noexcept;
|
||||
|
||||
constexpr const TileSheet::SubSheet *activeSubSheet() const noexcept {
|
||||
auto &activeSubSheet = m_img.getSubSheet(m_activeSubsSheetIdx);
|
||||
return &activeSubSheet;
|
||||
}
|
||||
|
||||
constexpr TileSheet::SubSheet *activeSubSheet() noexcept {
|
||||
auto &activeSubSheet = m_img.getSubSheet(m_activeSubsSheetIdx);
|
||||
return &activeSubSheet;
|
||||
}
|
||||
|
||||
constexpr const TileSheet::SubSheetIdx &activeSubSheetIdx() const noexcept {
|
||||
return m_activeSubsSheetIdx;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
bool updated() const noexcept;
|
||||
|
||||
ox::Error markUpdated() noexcept;
|
||||
ox::Error markUpdated(int) noexcept;
|
||||
|
||||
void ackUpdate() noexcept;
|
||||
|
||||
ox::Error saveFile() noexcept;
|
||||
|
||||
constexpr studio::UndoStack *undoStack() noexcept {
|
||||
return &m_undoStack;
|
||||
}
|
||||
constexpr studio::UndoStack *undoStack() noexcept;
|
||||
|
||||
protected:
|
||||
void saveItem();
|
||||
@ -189,4 +323,8 @@ constexpr const Palette &TileSheetEditorModel::pal() const noexcept {
|
||||
return *m_pal;
|
||||
}
|
||||
|
||||
}
|
||||
constexpr studio::UndoStack *TileSheetEditorModel::undoStack() noexcept {
|
||||
return &m_undoStack;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2,35 +2,22 @@
|
||||
* Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <nostalgia/core/consts.hpp>
|
||||
#include <nostalgia/core/media.hpp>
|
||||
#include <nostalgia/geo/point.hpp>
|
||||
|
||||
#include "tilesheeteditor.hpp"
|
||||
#include "tilesheeteditorview.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
TileSheetEditor::TileSheetEditor(Context *ctx, const ox::String &path): m_model(ctx, path) {
|
||||
TileSheetEditorView::TileSheetEditorView(Context *ctx, const ox::String &path): m_model(ctx, path) {
|
||||
// build shaders
|
||||
oxThrowError(m_pixelsDrawer.buildShader());
|
||||
oxThrowError(m_pixelGridDrawer.buildShader());
|
||||
m_model.activeSubsheetChanged.connect(this, &TileSheetEditorView::setActiveSubsheet);
|
||||
}
|
||||
|
||||
void TileSheetEditor::cut() {
|
||||
m_model.cut();
|
||||
}
|
||||
|
||||
void TileSheetEditor::copy() {
|
||||
m_model.copy();
|
||||
}
|
||||
|
||||
void TileSheetEditor::paste() {
|
||||
m_model.paste();
|
||||
}
|
||||
|
||||
void TileSheetEditor::draw() noexcept {
|
||||
void TileSheetEditorView::draw() noexcept {
|
||||
constexpr Color32 bgColor = 0x717d7e;
|
||||
glClearColor(redf(bgColor), greenf(bgColor), bluef(bgColor), 1.f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
@ -38,10 +25,10 @@ void TileSheetEditor::draw() noexcept {
|
||||
m_pixelGridDrawer.draw(updated(), m_scrollOffset);
|
||||
}
|
||||
|
||||
void TileSheetEditor::scrollV(const geo::Vec2 &paneSz, float wheel, bool zoomMod) noexcept {
|
||||
void TileSheetEditorView::scrollV(const geo::Vec2 &paneSz, float wheel, bool zoomMod) noexcept {
|
||||
const auto pixelSize = m_pixelsDrawer.pixelSize(paneSz);
|
||||
const ImVec2 sheetSize(pixelSize.x * static_cast<float>(img().columns()) * TileWidth,
|
||||
pixelSize.y * static_cast<float>(img().rows()) * TileHeight);
|
||||
const ImVec2 sheetSize(pixelSize.x * static_cast<float>(m_model.activeSubSheet()->columns) * TileWidth,
|
||||
pixelSize.y * static_cast<float>(m_model.activeSubSheet()->rows) * TileHeight);
|
||||
if (zoomMod) {
|
||||
m_pixelSizeMod = ox::clamp(m_pixelSizeMod + wheel * 0.02f, 0.55f, 2.f);
|
||||
m_pixelsDrawer.setPixelSizeMod(m_pixelSizeMod);
|
||||
@ -55,15 +42,15 @@ void TileSheetEditor::scrollV(const geo::Vec2 &paneSz, float wheel, bool zoomMod
|
||||
m_scrollOffset.y = ox::clamp(m_scrollOffset.y, 0.f, sheetSize.y / 2);
|
||||
}
|
||||
|
||||
void TileSheetEditor::scrollH(const geo::Vec2 &paneSz, float wheelh) noexcept {
|
||||
void TileSheetEditorView::scrollH(const geo::Vec2 &paneSz, float wheelh) noexcept {
|
||||
const auto pixelSize = m_pixelsDrawer.pixelSize(paneSz);
|
||||
const ImVec2 sheetSize(pixelSize.x * static_cast<float>(img().columns()) * TileWidth,
|
||||
pixelSize.y * static_cast<float>(img().rows()) * TileHeight);
|
||||
const ImVec2 sheetSize(pixelSize.x * static_cast<float>(m_model.activeSubSheet()->columns) * TileWidth,
|
||||
pixelSize.y * static_cast<float>(m_model.activeSubSheet()->rows) * TileHeight);
|
||||
m_scrollOffset.x += wheelh * 0.1f;
|
||||
m_scrollOffset.x = ox::clamp(m_scrollOffset.x, -(sheetSize.x / 2), 0.f);
|
||||
}
|
||||
|
||||
void TileSheetEditor::click(const geo::Vec2 &paneSize, const geo::Vec2 &clickPos) noexcept {
|
||||
void TileSheetEditorView::click(const geo::Vec2 &paneSize, const geo::Vec2 &clickPos) noexcept {
|
||||
auto [x, y] = clickPos;
|
||||
const auto pixDrawSz = m_pixelsDrawer.pixelSize(paneSize);
|
||||
x /= paneSize.x;
|
||||
@ -76,25 +63,32 @@ void TileSheetEditor::click(const geo::Vec2 &paneSize, const geo::Vec2 &clickPos
|
||||
m_model.drawCommand(pt, m_palIdx);
|
||||
}
|
||||
|
||||
void TileSheetEditor::releaseMouseButton() noexcept {
|
||||
void TileSheetEditorView::releaseMouseButton() noexcept {
|
||||
m_model.endDrawCommand();
|
||||
}
|
||||
|
||||
void TileSheetEditor::resizeView(const geo::Vec2 &sz) noexcept {
|
||||
m_pixelsDrawer.initBufferSet(sz, img(), pal());
|
||||
m_pixelGridDrawer.initBufferSet(sz, img());
|
||||
void TileSheetEditorView::resizeView(const geo::Vec2 &sz) noexcept {
|
||||
m_viewSize = sz;
|
||||
initView();
|
||||
}
|
||||
|
||||
bool TileSheetEditor::updated() const noexcept {
|
||||
bool TileSheetEditorView::updated() const noexcept {
|
||||
return m_updated || m_model.updated();
|
||||
}
|
||||
|
||||
void TileSheetEditor::ackUpdate() noexcept {
|
||||
void TileSheetEditorView::ackUpdate() noexcept {
|
||||
m_updated = false;
|
||||
m_model.ackUpdate();
|
||||
}
|
||||
|
||||
void TileSheetEditor::saveItem() {
|
||||
void TileSheetEditorView::initView() noexcept {
|
||||
m_pixelsDrawer.initBufferSet(m_viewSize, img(), *m_model.activeSubSheet(), pal());
|
||||
m_pixelGridDrawer.initBufferSet(m_viewSize, *m_model.activeSubSheet());
|
||||
}
|
||||
|
||||
ox::Error TileSheetEditorView::setActiveSubsheet(const TileSheet::SubSheetIdx&) noexcept {
|
||||
initView();
|
||||
return OxError(0);
|
||||
}
|
||||
|
||||
}
|
@ -36,27 +36,22 @@ constexpr auto toString(TileSheetTool t) noexcept {
|
||||
return "";
|
||||
}
|
||||
|
||||
class TileSheetEditor {
|
||||
class TileSheetEditorView: public ox::SignalHandler {
|
||||
|
||||
private:
|
||||
TileSheetEditorModel m_model;
|
||||
TileSheetGrid m_pixelGridDrawer;
|
||||
TileSheetPixels m_pixelsDrawer;
|
||||
geo::Vec2 m_viewSize;
|
||||
float m_pixelSizeMod = 1;
|
||||
bool m_updated = false;
|
||||
geo::Vec2 m_scrollOffset;
|
||||
std::size_t m_palIdx = 0;
|
||||
|
||||
public:
|
||||
TileSheetEditor(Context *ctx, const ox::String &path);
|
||||
TileSheetEditorView(Context *ctx, const ox::String &path);
|
||||
|
||||
~TileSheetEditor() = default;
|
||||
|
||||
void cut();
|
||||
|
||||
void copy();
|
||||
|
||||
void paste();
|
||||
~TileSheetEditorView() override = default;
|
||||
|
||||
void draw() noexcept;
|
||||
|
||||
@ -73,6 +68,9 @@ class TileSheetEditor {
|
||||
[[nodiscard]]
|
||||
constexpr const TileSheet &img() const noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr TileSheet &img() noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr const Palette &pal() const noexcept;
|
||||
|
||||
@ -100,14 +98,9 @@ class TileSheetEditor {
|
||||
|
||||
void ackUpdate() noexcept;
|
||||
|
||||
constexpr studio::UndoStack *undoStack() noexcept {
|
||||
return m_model.undoStack();
|
||||
}
|
||||
|
||||
protected:
|
||||
void saveItem();
|
||||
|
||||
private:
|
||||
void initView() noexcept;
|
||||
|
||||
void setPalette();
|
||||
|
||||
void saveState();
|
||||
@ -121,6 +114,8 @@ class TileSheetEditor {
|
||||
ox::String palettePath(const ox::String &palettePath) const;
|
||||
|
||||
// slots
|
||||
ox::Error setActiveSubsheet(const TileSheet::SubSheetIdx &idx) noexcept;
|
||||
|
||||
public:
|
||||
ox::Error colorSelected() noexcept;
|
||||
|
||||
@ -128,11 +123,15 @@ class TileSheetEditor {
|
||||
|
||||
};
|
||||
|
||||
constexpr const TileSheet &TileSheetEditor::img() const noexcept {
|
||||
constexpr const TileSheet &TileSheetEditorView::img() const noexcept {
|
||||
return m_model.img();
|
||||
}
|
||||
|
||||
constexpr const Palette &TileSheetEditor::pal() const noexcept {
|
||||
constexpr TileSheet &TileSheetEditorView::img() noexcept {
|
||||
return m_model.img();
|
||||
}
|
||||
|
||||
constexpr const Palette &TileSheetEditorView::pal() const noexcept {
|
||||
return m_model.pal();
|
||||
}
|
||||
|
@ -30,13 +30,13 @@ void TileSheetGrid::draw(bool update, const geo::Vec2 &scroll) noexcept {
|
||||
glDrawArrays(GL_POINTS, 0, static_cast<GLsizei>(m_bufferSet.vertices.size() / VertexVboRowLength));
|
||||
}
|
||||
|
||||
void TileSheetGrid::initBufferSet(const geo::Vec2 &paneSize, const TileSheet &img) noexcept {
|
||||
void TileSheetGrid::initBufferSet(const geo::Vec2 &paneSize, const TileSheet::SubSheet &subsheet) noexcept {
|
||||
// vao
|
||||
m_bufferSet.vao = glutils::generateVertexArrayObject();
|
||||
glBindVertexArray(m_bufferSet.vao);
|
||||
// vbo
|
||||
m_bufferSet.vbo = glutils::generateBuffer();
|
||||
setBufferObjects(paneSize, img, &m_bufferSet);
|
||||
setBufferObjects(paneSize, subsheet, &m_bufferSet);
|
||||
glutils::sendVbo(m_bufferSet);
|
||||
// vbo layout
|
||||
const auto pt1Attr = static_cast<GLuint>(glGetAttribLocation(m_shader, "vPt1"));
|
||||
@ -62,38 +62,38 @@ void TileSheetGrid::setBufferObject(geo::Point pt1, geo::Point pt2, Color32 c, f
|
||||
memcpy(vbo, vertices, sizeof(vertices));
|
||||
}
|
||||
|
||||
void TileSheetGrid::setBufferObjects(const geo::Vec2 &paneSize, const TileSheet &img, glutils::BufferSet *bg) noexcept {
|
||||
void TileSheetGrid::setBufferObjects(const geo::Vec2 &paneSize, const TileSheet::SubSheet &subsheet, glutils::BufferSet *bg) noexcept {
|
||||
const auto pixSize = pixelSize(paneSize);
|
||||
const auto set = [bg, pixSize](unsigned i, geo::Point pt1, geo::Point pt2, Color32 c) {
|
||||
const auto vbo = &bg->vertices[i * VertexVboLength];
|
||||
setBufferObject(pt1, pt2, c, vbo, pixSize);
|
||||
};
|
||||
// set buffer length
|
||||
const auto width = img.columns() * TileWidth;
|
||||
const auto height = img.rows() * TileHeight;
|
||||
const auto pixelCnt = static_cast<unsigned>(width * height);
|
||||
const auto tileCnt = static_cast<unsigned>(img.columns() * img.rows());
|
||||
m_bufferSet.vertices.resize((tileCnt + pixelCnt) * VertexVboLength);
|
||||
const auto width = subsheet.columns * TileWidth;
|
||||
const auto height = subsheet.rows * TileHeight;
|
||||
const auto tileCnt = static_cast<unsigned>(subsheet.columns + subsheet.rows);
|
||||
const auto pixelCnt = static_cast<unsigned>(width + height);
|
||||
m_bufferSet.vertices.resize((tileCnt + pixelCnt + 4) * VertexVboLength);
|
||||
// set buffer
|
||||
auto i = 0ull;
|
||||
// pixel outlines
|
||||
constexpr auto pixOutlineColor = color32(0.4431f, 0.4901f, 0.4941f);
|
||||
for (auto x = 0; x < img.columns() * TileWidth + 1; ++x) {
|
||||
set(i, {x, 0}, {x, img.rows() * TileHeight}, pixOutlineColor);
|
||||
for (auto x = 0; x < subsheet.columns * TileWidth + 1; ++x) {
|
||||
set(i, {x, 0}, {x, subsheet.rows * TileHeight}, pixOutlineColor);
|
||||
++i;
|
||||
}
|
||||
for (auto y = 0; y < img.rows() * TileHeight + 1; ++y) {
|
||||
set(i, {0, y}, {img.columns() * TileWidth, y}, pixOutlineColor);
|
||||
for (auto y = 0; y < subsheet.rows * TileHeight + 1; ++y) {
|
||||
set(i, {0, y}, {subsheet.columns * TileWidth, y}, pixOutlineColor);
|
||||
++i;
|
||||
}
|
||||
// tile outlines
|
||||
constexpr auto tileOutlineColor = color32(0.f, 0.f, 0.f);
|
||||
for (auto x = 0; x < img.columns() * TileWidth + 1; x += TileWidth) {
|
||||
set(i, {x, 0}, {x, img.rows() * TileHeight}, tileOutlineColor);
|
||||
for (auto x = 0; x < subsheet.columns * TileWidth + 1; x += TileWidth) {
|
||||
set(i, {x, 0}, {x, subsheet.rows * TileHeight}, tileOutlineColor);
|
||||
++i;
|
||||
}
|
||||
for (auto y = 0; y < img.rows() * TileHeight + 1; y += TileHeight) {
|
||||
set(i, {0, y}, {img.columns() * TileWidth, y}, tileOutlineColor);
|
||||
for (auto y = 0; y < subsheet.rows * TileHeight + 1; y += TileHeight) {
|
||||
set(i, {0, y}, {subsheet.columns * TileWidth, y}, tileOutlineColor);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
@ -69,12 +69,12 @@ class TileSheetGrid {
|
||||
|
||||
void draw(bool update, const geo::Vec2 &scroll) noexcept;
|
||||
|
||||
void initBufferSet(const geo::Vec2 &paneSize, const TileSheet &img) noexcept;
|
||||
void initBufferSet(const geo::Vec2 &paneSize, const TileSheet::SubSheet &subsheet) noexcept;
|
||||
|
||||
private:
|
||||
static void setBufferObject(geo::Point pt1, geo::Point pt2, Color32 c, float *vbo, const geo::Vec2 &pixSize) noexcept;
|
||||
|
||||
void setBufferObjects(const geo::Vec2 &paneSize, const TileSheet &img, glutils::BufferSet *bg) noexcept;
|
||||
void setBufferObjects(const geo::Vec2 &paneSize, const TileSheet::SubSheet &subsheet, glutils::BufferSet *bg) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
geo::Vec2 pixelSize(const geo::Vec2 &paneSize) const noexcept;
|
||||
|
@ -29,14 +29,14 @@ void TileSheetPixels::draw(bool update, const geo::Vec2 &scroll) noexcept {
|
||||
glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(m_bufferSet.elements.size()), GL_UNSIGNED_INT, nullptr);
|
||||
}
|
||||
|
||||
void TileSheetPixels::initBufferSet(const geo::Vec2 &paneSize, const TileSheet &img, const Palette &pal) noexcept {
|
||||
void TileSheetPixels::initBufferSet(const geo::Vec2 &paneSize, const TileSheet &img, const TileSheet::SubSheet &subSheet, const Palette &pal) noexcept {
|
||||
// vao
|
||||
m_bufferSet.vao = glutils::generateVertexArrayObject();
|
||||
glBindVertexArray(m_bufferSet.vao);
|
||||
// vbo & ebo
|
||||
m_bufferSet.vbo = glutils::generateBuffer();
|
||||
m_bufferSet.ebo = glutils::generateBuffer();
|
||||
setBufferObjects(paneSize, img, pal, &m_bufferSet);
|
||||
setBufferObjects(paneSize, img, subSheet, pal, &m_bufferSet);
|
||||
glutils::sendVbo(m_bufferSet);
|
||||
glutils::sendEbo(m_bufferSet);
|
||||
// vbo layout
|
||||
@ -78,10 +78,10 @@ void TileSheetPixels::setPixelBufferObject(const geo::Vec2 &paneSize, unsigned v
|
||||
memcpy(ebo, elms, sizeof(elms));
|
||||
}
|
||||
|
||||
void TileSheetPixels::setBufferObjects(const geo::Vec2 &paneSize, const TileSheet &img, const Palette &pal, glutils::BufferSet *bg) noexcept {
|
||||
const auto setPixel = [this, paneSize, bg, img, pal](std::size_t i, uint8_t p) {
|
||||
void TileSheetPixels::setBufferObjects(const geo::Vec2 &paneSize, const TileSheet &img, const TileSheet::SubSheet &subSheet, const Palette &pal, glutils::BufferSet *bg) noexcept {
|
||||
const auto setPixel = [this, paneSize, bg, subSheet, pal](std::size_t i, uint8_t p) {
|
||||
const auto color = pal.colors[p];
|
||||
const auto pt = idxToPt(static_cast<int>(i), img.columns());
|
||||
const auto pt = idxToPt(static_cast<int>(i), subSheet.columns);
|
||||
const auto fx = static_cast<float>(pt.x);
|
||||
const auto fy = static_cast<float>(pt.y);
|
||||
const auto vbo = &bg->vertices[i * VertexVboLength];
|
||||
@ -89,22 +89,22 @@ void TileSheetPixels::setBufferObjects(const geo::Vec2 &paneSize, const TileShee
|
||||
setPixelBufferObject(paneSize, i * VertexVboRows, fx, fy, color, vbo, ebo);
|
||||
};
|
||||
// set buffer lengths
|
||||
const auto width = img.columns() * TileWidth;
|
||||
const auto height = img.rows() * TileHeight;
|
||||
const auto width = subSheet.columns * TileWidth;
|
||||
const auto height = subSheet.rows * TileHeight;
|
||||
const auto tiles = static_cast<unsigned>(width * height);
|
||||
m_bufferSet.vertices.resize(tiles * VertexVboLength);
|
||||
m_bufferSet.elements.resize(tiles * VertexEboLength);
|
||||
// set pixels
|
||||
if (img.bpp == 4) {
|
||||
for (std::size_t i = 0; i < img.pixels.size(); ++i) {
|
||||
const auto colorIdx1 = img.pixels[i] & 0xF;
|
||||
const auto colorIdx2 = img.pixels[i] >> 4;
|
||||
for (std::size_t i = 0; i < subSheet.pixels.size(); ++i) {
|
||||
const auto colorIdx1 = subSheet.pixels[i] & 0xF;
|
||||
const auto colorIdx2 = subSheet.pixels[i] >> 4;
|
||||
setPixel(i * 2 + 0, colorIdx1);
|
||||
setPixel(i * 2 + 1, colorIdx2);
|
||||
}
|
||||
} else {
|
||||
for (std::size_t i = 0; i < img.pixels.size(); ++i) {
|
||||
const auto p = img.pixels[i];
|
||||
for (std::size_t i = 0; i < subSheet.pixels.size(); ++i) {
|
||||
const auto p = subSheet.pixels[i];
|
||||
setPixel(i, p);
|
||||
}
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ class TileSheetPixels {
|
||||
|
||||
void draw(bool update, const geo::Vec2 &scroll) noexcept;
|
||||
|
||||
void initBufferSet(const geo::Vec2 &paneSize, const TileSheet &img, const Palette &pal) noexcept;
|
||||
void initBufferSet(const geo::Vec2 &paneSize, const TileSheet &img, const TileSheet::SubSheet &subSheet, const Palette &pal) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
geo::Vec2 pixelSize(const geo::Vec2 &paneSize) const noexcept;
|
||||
@ -56,7 +56,7 @@ class TileSheetPixels {
|
||||
private:
|
||||
void setPixelBufferObject(const geo::Vec2 &paneS, unsigned vertexRow, float x, float y, Color16 color, float *vbo, GLuint *ebo) const noexcept;
|
||||
|
||||
void setBufferObjects(const geo::Vec2 &paneS, const TileSheet &img, const Palette &pal, glutils::BufferSet *bg) noexcept;
|
||||
void setBufferObjects(const geo::Vec2 &paneS, const TileSheet &img, const TileSheet::SubSheet &subSheet, const Palette &pal, glutils::BufferSet *bg) noexcept;
|
||||
|
||||
};
|
||||
|
||||
|
@ -15,7 +15,7 @@ struct NostalgiaGraphicToTileSheetConverter: public Converter<NostalgiaGraphic,
|
||||
dst->subsheet.rows = src->rows;
|
||||
dst->subsheet.columns = src->columns;
|
||||
dst->defaultPalette = std::move(src->defaultPalette);
|
||||
dst->pixels = std::move(src->pixels);
|
||||
dst->subsheet.pixels = std::move(src->pixels);
|
||||
return OxError(0);
|
||||
}
|
||||
};
|
||||
@ -31,7 +31,7 @@ struct TileSheetToCompactTileSheetConverter: public Converter<TileSheet, Compact
|
||||
ox::Error convert(TileSheet *src, CompactTileSheet *dst) noexcept final {
|
||||
dst->bpp = src->bpp;
|
||||
dst->defaultPalette = std::move(src->defaultPalette);
|
||||
dst->pixels = std::move(src->pixels);
|
||||
dst->pixels = src->pixels();
|
||||
return OxError(0);
|
||||
}
|
||||
};
|
||||
|
@ -27,7 +27,7 @@ ox::Error loadBgTileSheet(Context *ctx,
|
||||
int section,
|
||||
const ox::FileAddress &tilesheetPath,
|
||||
const ox::FileAddress &palettePath) noexcept {
|
||||
oxRequire(tilesheet, readObj<TileSheet>(ctx, tilesheetPath));
|
||||
oxRequire(tilesheet, readObj<CompactTileSheet>(ctx, tilesheetPath));
|
||||
oxRequire(palette, readObj<Palette>(ctx, palettePath ? palettePath : tilesheet->defaultPalette));
|
||||
const unsigned bytesPerTile = tilesheet->bpp == 8 ? 64 : 32;
|
||||
const auto tiles = tilesheet->pixels.size() / bytesPerTile;
|
||||
|
@ -50,8 +50,7 @@ class NOSTALGIASTUDIO_EXPORT Project {
|
||||
/**
|
||||
* Writes a MetalClaw object to the project at the given path.
|
||||
*/
|
||||
template<typename T>
|
||||
ox::Error writeObj(const ox::String &path, T *obj) const noexcept;
|
||||
ox::Error writeObj(const ox::String &path, auto *obj) const noexcept;
|
||||
|
||||
template<typename T>
|
||||
ox::Result<ox::UniquePtr<T>> loadObj(const ox::String &path) const noexcept;
|
||||
@ -86,8 +85,7 @@ class NOSTALGIASTUDIO_EXPORT Project {
|
||||
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
ox::Error Project::writeObj(const ox::String &path, T *obj) const noexcept {
|
||||
ox::Error Project::writeObj(const ox::String &path, auto *obj) const noexcept {
|
||||
// write MetalClaw
|
||||
oxRequireM(buff, ox::writeClaw(obj, ox::ClawFormat::Metal));
|
||||
// write to FS
|
||||
|
@ -17,7 +17,7 @@ enum class TaskState {
|
||||
|
||||
class Task: public ox::SignalHandler {
|
||||
public:
|
||||
virtual ~Task() noexcept = default;
|
||||
~Task() noexcept override = default;
|
||||
virtual TaskState update(nostalgia::core::Context *ctx) noexcept = 0;
|
||||
};
|
||||
|
||||
|
@ -12,25 +12,30 @@ void UndoStack::push(UndoCommand *cmd) noexcept {
|
||||
}
|
||||
m_stack.emplace_back(cmd);
|
||||
++m_stackIdx;
|
||||
auto cmdId = cmd->commandId();
|
||||
cmd->redo();
|
||||
redoTriggered.emit();
|
||||
changeTriggered.emit();
|
||||
redoTriggered.emit(cmdId);
|
||||
changeTriggered.emit(cmdId);
|
||||
}
|
||||
|
||||
void UndoStack::redo() noexcept {
|
||||
if (m_stackIdx < m_stack.size()) {
|
||||
m_stack[m_stackIdx++]->redo();
|
||||
auto &c = m_stack[m_stackIdx++];
|
||||
auto cmdId = c->commandId();
|
||||
c->redo();
|
||||
redoTriggered.emit(cmdId);
|
||||
changeTriggered.emit(cmdId);
|
||||
}
|
||||
redoTriggered.emit();
|
||||
changeTriggered.emit();
|
||||
}
|
||||
|
||||
void UndoStack::undo() noexcept {
|
||||
if (m_stackIdx) {
|
||||
m_stack[--m_stackIdx]->undo();
|
||||
auto &c = m_stack[--m_stackIdx];
|
||||
auto cmdId = c->commandId();
|
||||
c->undo();
|
||||
undoTriggered.emit(cmdId);
|
||||
changeTriggered.emit(cmdId);
|
||||
}
|
||||
undoTriggered.emit();
|
||||
changeTriggered.emit();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -16,6 +16,8 @@ class UndoCommand {
|
||||
virtual ~UndoCommand() noexcept = default;
|
||||
virtual void redo() noexcept = 0;
|
||||
virtual void undo() noexcept = 0;
|
||||
[[nodiscard]]
|
||||
virtual int commandId() const noexcept = 0;
|
||||
};
|
||||
|
||||
class UndoStack {
|
||||
@ -40,9 +42,9 @@ class UndoStack {
|
||||
return m_stackIdx;
|
||||
}
|
||||
|
||||
ox::Signal<ox::Error()> redoTriggered;
|
||||
ox::Signal<ox::Error()> undoTriggered;
|
||||
ox::Signal<ox::Error()> changeTriggered;
|
||||
ox::Signal<ox::Error(int)> redoTriggered;
|
||||
ox::Signal<ox::Error(int)> undoTriggered;
|
||||
ox::Signal<ox::Error(int)> changeTriggered;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -12,7 +12,8 @@ namespace nostalgia::studio {
|
||||
|
||||
class Widget: public ox::SignalHandler {
|
||||
public:
|
||||
~Widget() noexcept override = default;
|
||||
virtual void draw(core::Context*) noexcept = 0;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user