[nostalgia] Add basic support for subsheets

This commit is contained in:
Gary Talent 2022-02-26 22:48:18 -06:00
parent 329ecb3266
commit e8a046c2dc
20 changed files with 630 additions and 238 deletions

View File

@ -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)

View File

@ -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()

View File

@ -6,7 +6,7 @@ add_library(
#newpalettewizard.cpp
#paletteeditor.cpp
tilesheeteditor-imgui.cpp
tilesheeteditor.cpp
tilesheeteditorview.cpp
tilesheeteditormodel.cpp
tilesheetpixelgrid.cpp
tilesheetpixels.cpp

View File

@ -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);
}

View File

@ -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;
};

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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;
};

View File

@ -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);
}
};

View File

@ -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;

View File

@ -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

View File

@ -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;
};

View File

@ -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();
}
}

View File

@ -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;
};
}

View File

@ -12,7 +12,8 @@ namespace nostalgia::studio {
class Widget: public ox::SignalHandler {
public:
~Widget() noexcept override = default;
virtual void draw(core::Context*) noexcept = 0;
};
}
}