This commit is contained in:
@@ -1,4 +1,9 @@
|
||||
|
||||
if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang")
|
||||
# enable warnings
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wunsafe-buffer-usage")
|
||||
endif()
|
||||
|
||||
project(nostalgia CXX)
|
||||
|
||||
#project packages
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/std/math.hpp>
|
||||
#include <ox/std/types.hpp>
|
||||
|
||||
namespace nostalgia::core {
|
||||
@@ -135,7 +136,7 @@ constexpr Color16 color16(int r, int g, int b, int a = 0) noexcept {
|
||||
return static_cast<Color16>(ox::min<uint8_t>(static_cast<uint8_t>(r), 31))
|
||||
| static_cast<Color16>(ox::min<uint8_t>(static_cast<uint8_t>(g), 31) << 5)
|
||||
| static_cast<Color16>(ox::min<uint8_t>(static_cast<uint8_t>(b), 31) << 10)
|
||||
| static_cast<Color16>(a << 15);
|
||||
| static_cast<Color16>(a << 15);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
@@ -151,4 +152,13 @@ static_assert(color16(16, 31, 0) == 1008);
|
||||
static_assert(color16(16, 31, 8) == 9200);
|
||||
static_assert(color16(16, 32, 8) == 9200);
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr Color16 applySelectionColor(Color16 const color) noexcept {
|
||||
namespace core = nostalgia::core;
|
||||
auto const r = core::red16(color) / 2;
|
||||
auto const g = (core::green16(color) + 20) / 2;
|
||||
auto const b = (core::blue16(color) + 31) / 2;
|
||||
return core::color16(r, g, b);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include "context.hpp"
|
||||
#include "palette.hpp"
|
||||
#include "tilesheet.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
@@ -31,18 +32,18 @@ struct Sprite {
|
||||
unsigned priority = 0;
|
||||
};
|
||||
|
||||
oxModelBegin(Sprite)
|
||||
oxModelField(idx)
|
||||
oxModelField(x)
|
||||
oxModelField(y)
|
||||
oxModelField(enabled)
|
||||
oxModelField(tileIdx)
|
||||
oxModelField(spriteShape)
|
||||
oxModelField(spriteSize)
|
||||
oxModelField(flipX)
|
||||
oxModelField(bpp)
|
||||
oxModelField(priority)
|
||||
oxModelEnd()
|
||||
OX_MODEL_BEGIN(Sprite)
|
||||
OX_MODEL_FIELD(idx)
|
||||
OX_MODEL_FIELD(x)
|
||||
OX_MODEL_FIELD(y)
|
||||
OX_MODEL_FIELD(enabled)
|
||||
OX_MODEL_FIELD(tileIdx)
|
||||
OX_MODEL_FIELD(spriteShape)
|
||||
OX_MODEL_FIELD(spriteSize)
|
||||
OX_MODEL_FIELD(flipX)
|
||||
OX_MODEL_FIELD(bpp)
|
||||
OX_MODEL_FIELD(priority)
|
||||
OX_MODEL_END()
|
||||
|
||||
struct BgTile {
|
||||
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.BgTile";
|
||||
@@ -53,12 +54,12 @@ struct BgTile {
|
||||
unsigned flipY = false;
|
||||
};
|
||||
|
||||
oxModelBegin(BgTile)
|
||||
oxModelField(tileIdx)
|
||||
oxModelField(palBank)
|
||||
oxModelField(horizontalFlip)
|
||||
oxModelField(verticalFlip)
|
||||
oxModelEnd()
|
||||
OX_MODEL_BEGIN(BgTile)
|
||||
OX_MODEL_FIELD(tileIdx)
|
||||
OX_MODEL_FIELD(palBank)
|
||||
OX_MODEL_FIELD(horizontalFlip)
|
||||
OX_MODEL_FIELD(verticalFlip)
|
||||
OX_MODEL_END()
|
||||
|
||||
struct TileSheetSetEntrySection {
|
||||
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.TileSheetSetEntrySection";
|
||||
@@ -71,10 +72,10 @@ struct TileSheetSetEntrySection {
|
||||
}
|
||||
};
|
||||
|
||||
oxModelBegin(TileSheetSetEntrySection)
|
||||
oxModelField(begin)
|
||||
oxModelField(tiles)
|
||||
oxModelEnd()
|
||||
OX_MODEL_BEGIN(TileSheetSetEntrySection)
|
||||
OX_MODEL_FIELD(begin)
|
||||
OX_MODEL_FIELD(tiles)
|
||||
OX_MODEL_END()
|
||||
|
||||
struct TileSheetSetEntry {
|
||||
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.TileSheetSetEntry";
|
||||
@@ -83,10 +84,10 @@ struct TileSheetSetEntry {
|
||||
ox::Vector<TileSheetSetEntrySection> sections;
|
||||
};
|
||||
|
||||
oxModelBegin(TileSheetSetEntry)
|
||||
oxModelField(tilesheet)
|
||||
oxModelField(sections)
|
||||
oxModelEnd()
|
||||
OX_MODEL_BEGIN(TileSheetSetEntry)
|
||||
OX_MODEL_FIELD(tilesheet)
|
||||
OX_MODEL_FIELD(sections)
|
||||
OX_MODEL_END()
|
||||
|
||||
struct TileSheetSet {
|
||||
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.TileSheetSet";
|
||||
@@ -96,29 +97,44 @@ struct TileSheetSet {
|
||||
ox::Vector<TileSheetSetEntry> entries;
|
||||
};
|
||||
|
||||
oxModelBegin(TileSheetSet)
|
||||
oxModelField(bpp)
|
||||
oxModelField(entries)
|
||||
oxModelEnd()
|
||||
OX_MODEL_BEGIN(TileSheetSet)
|
||||
OX_MODEL_FIELD(bpp)
|
||||
OX_MODEL_FIELD(entries)
|
||||
OX_MODEL_END()
|
||||
|
||||
void addEntry(TileSheetSet &set, ox::FileAddress path, int32_t begin = 0, int32_t size = -1) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
int tileColumns(Context&) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
int tileRows(Context&) noexcept;
|
||||
|
||||
ox::Error loadBgPalette(
|
||||
Context &ctx,
|
||||
size_t palBank,
|
||||
Palette const&palette,
|
||||
CompactPalette const&palette,
|
||||
size_t page = 0) noexcept;
|
||||
|
||||
ox::Error loadSpritePalette(
|
||||
Context &ctx,
|
||||
Palette const&palette,
|
||||
CompactPalette const&palette,
|
||||
size_t page = 0) noexcept;
|
||||
|
||||
ox::Error loadBgPalette(
|
||||
Context &ctx,
|
||||
size_t palBank,
|
||||
ox::StringViewCR palettePath) noexcept;
|
||||
|
||||
ox::Error loadBgPalette(
|
||||
Context &ctx,
|
||||
size_t palBank,
|
||||
ox::FileAddress const&paletteAddr) noexcept;
|
||||
|
||||
ox::Error loadSpritePalette(
|
||||
Context &ctx,
|
||||
ox::StringViewCR palettePath) noexcept;
|
||||
|
||||
ox::Error loadSpritePalette(
|
||||
Context &ctx,
|
||||
ox::FileAddress const&paletteAddr) noexcept;
|
||||
@@ -128,12 +144,58 @@ ox::Error loadBgTileSheet(
|
||||
unsigned cbb,
|
||||
TileSheetSet const&set) noexcept;
|
||||
|
||||
ox::Error loadBgTileSheet(
|
||||
Context &ctx,
|
||||
unsigned cbb,
|
||||
CompactTileSheet const&ts,
|
||||
size_t dstTileIdx,
|
||||
size_t srcTileIdx,
|
||||
size_t tileCnt) noexcept;
|
||||
|
||||
ox::Error loadBgTileSheet(
|
||||
Context &ctx,
|
||||
unsigned cbb,
|
||||
ox::StringViewCR tsPath,
|
||||
size_t dstTileIdx,
|
||||
size_t srcTileIdx,
|
||||
size_t tileCnt) noexcept;
|
||||
|
||||
ox::Error loadBgTileSheet(
|
||||
Context &ctx,
|
||||
unsigned cbb,
|
||||
ox::FileAddress const&tsAddr,
|
||||
size_t dstTileIdx,
|
||||
size_t srcTileIdx,
|
||||
size_t tileCnt) noexcept;
|
||||
|
||||
ox::Error loadBgTileSheet(
|
||||
Context &ctx,
|
||||
unsigned cbb,
|
||||
CompactTileSheet const&ts,
|
||||
ox::Optional<unsigned> const&paletteBank = {}) noexcept;
|
||||
|
||||
ox::Error loadBgTileSheet(
|
||||
Context &ctx,
|
||||
unsigned cbb,
|
||||
ox::StringViewCR tilesheetPath,
|
||||
ox::Optional<unsigned> const&paletteBank) noexcept;
|
||||
|
||||
ox::Error loadBgTileSheet(
|
||||
Context &ctx,
|
||||
unsigned cbb,
|
||||
ox::FileAddress const&tilesheetAddr,
|
||||
ox::Optional<unsigned> const&paletteBank = {}) noexcept;
|
||||
|
||||
ox::Error loadSpriteTileSheet(
|
||||
Context &ctx,
|
||||
CompactTileSheet const&ts,
|
||||
bool loadDefaultPalette) noexcept;
|
||||
|
||||
ox::Error loadSpriteTileSheet(
|
||||
Context &ctx,
|
||||
ox::StringViewCR tilesheetPath,
|
||||
bool loadDefaultPalette = false) noexcept;
|
||||
|
||||
ox::Error loadSpriteTileSheet(
|
||||
Context &ctx,
|
||||
ox::FileAddress const&tilesheetAddr,
|
||||
@@ -174,7 +236,7 @@ uint_t spriteCount(Context &ctx) noexcept;
|
||||
|
||||
ox::Error initConsole(Context &ctx) noexcept;
|
||||
|
||||
void puts(Context &ctx, int column, int row, ox::CRStringView str) noexcept;
|
||||
void puts(Context &ctx, int column, int row, ox::StringViewCR str) noexcept;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/std/types.hpp>
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
struct InitParams {
|
||||
@@ -12,4 +14,4 @@ struct InitParams {
|
||||
uint_t glBlocksPerSprite = 64;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/std/array.hpp>
|
||||
#include <ox/std/point.hpp>
|
||||
#include <ox/std/size.hpp>
|
||||
#include <ox/std/types.hpp>
|
||||
#include <ox/std/vector.hpp>
|
||||
#include <ox/model/def.hpp>
|
||||
|
||||
@@ -17,18 +13,76 @@
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
struct PaletteColorV1 {
|
||||
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.PaletteColor";
|
||||
static constexpr auto TypeVersion = 1;
|
||||
uint8_t r{}, g{}, b{}, a{};
|
||||
constexpr PaletteColorV1() noexcept = default;
|
||||
constexpr PaletteColorV1(Color16 const c) noexcept:
|
||||
r{red16(c)},
|
||||
g{green16(c)},
|
||||
b{blue16(c)},
|
||||
a{alpha16(c)} {}
|
||||
constexpr operator Color16() const noexcept { return color16(r, g, b, a); }
|
||||
};
|
||||
|
||||
OX_MODEL_BEGIN(PaletteColorV1)
|
||||
OX_MODEL_FIELD(r)
|
||||
OX_MODEL_FIELD(g)
|
||||
OX_MODEL_FIELD(b)
|
||||
OX_MODEL_FIELD(a)
|
||||
OX_MODEL_END()
|
||||
|
||||
using PaletteColor = PaletteColorV1;
|
||||
|
||||
|
||||
struct PalettePageV1 {
|
||||
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.Palette.PalettePage";
|
||||
static constexpr auto TypeVersion = 1;
|
||||
ox::String name;
|
||||
ox::Vector<PaletteColorV1> colors;
|
||||
constexpr PalettePageV1() noexcept = default;
|
||||
constexpr PalettePageV1(ox::StringParam pName, ox::Vector<PaletteColorV1> pColors) noexcept:
|
||||
name(std::move(pName)), colors(std::move(pColors)) {}
|
||||
constexpr PalettePageV1(ox::StringParam pName, ox::Vector<Color16> const&pColors) noexcept:
|
||||
name(std::move(pName)) {
|
||||
colors.reserve(pColors.size());
|
||||
for (auto const c : pColors) {
|
||||
colors.emplace_back(c);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
OX_MODEL_BEGIN(PalettePageV1)
|
||||
OX_MODEL_FIELD(name)
|
||||
OX_MODEL_FIELD(colors)
|
||||
OX_MODEL_END()
|
||||
|
||||
using PalettePage = PalettePageV1;
|
||||
|
||||
|
||||
struct NostalgiaPalette {
|
||||
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.NostalgiaPalette";
|
||||
static constexpr auto TypeVersion = 1;
|
||||
ox::Vector<Color16> colors = {};
|
||||
};
|
||||
|
||||
OX_MODEL_BEGIN(NostalgiaPalette)
|
||||
OX_MODEL_FIELD(colors)
|
||||
OX_MODEL_END()
|
||||
|
||||
|
||||
struct PaletteV1 {
|
||||
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.Palette";
|
||||
static constexpr auto TypeVersion = 1;
|
||||
ox::Vector<Color16> colors;
|
||||
};
|
||||
|
||||
OX_MODEL_BEGIN(PaletteV1)
|
||||
OX_MODEL_FIELD(colors)
|
||||
OX_MODEL_END()
|
||||
|
||||
|
||||
struct PaletteV2 {
|
||||
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.Palette";
|
||||
static constexpr auto TypeVersion = 2;
|
||||
@@ -36,10 +90,132 @@ struct PaletteV2 {
|
||||
ox::Vector<ox::Vector<Color16>> pages;
|
||||
};
|
||||
|
||||
using Palette = PaletteV2;
|
||||
OX_MODEL_BEGIN(PaletteV2)
|
||||
OX_MODEL_FIELD(pages)
|
||||
OX_MODEL_END()
|
||||
|
||||
|
||||
struct PaletteV3 {
|
||||
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.Palette";
|
||||
static constexpr auto TypeVersion = 3;
|
||||
static constexpr auto Preloadable = true;
|
||||
struct ColorInfo {
|
||||
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.Palette.ColorInfo";
|
||||
static constexpr auto TypeVersion = 3;
|
||||
ox::String name;
|
||||
constexpr ColorInfo() noexcept = default;
|
||||
constexpr ColorInfo(ox::StringParam pName) noexcept: name{std::move(pName)} {}
|
||||
};
|
||||
ox::Vector<ColorInfo> colorInfo;
|
||||
ox::Vector<ox::Vector<Color16>> pages;
|
||||
};
|
||||
|
||||
OX_MODEL_BEGIN(PaletteV3::ColorInfo)
|
||||
OX_MODEL_FIELD(name)
|
||||
OX_MODEL_END()
|
||||
|
||||
OX_MODEL_BEGIN(PaletteV3)
|
||||
OX_MODEL_FIELD(colorInfo)
|
||||
OX_MODEL_FIELD(pages)
|
||||
OX_MODEL_END()
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr bool valid(PaletteV3 const&p) noexcept {
|
||||
auto const colors = p.colorInfo.size();
|
||||
return ox::all_of(p.pages.begin(), p.pages.end(), [colors](auto const&page) {
|
||||
return page.size() == colors;
|
||||
});
|
||||
}
|
||||
|
||||
constexpr ox::Error repair(PaletteV3 &p) noexcept {
|
||||
auto const colors = p.colorInfo.size();
|
||||
for (auto &page : p.pages) {
|
||||
page.resize(colors);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
struct PaletteV4 {
|
||||
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.Palette";
|
||||
static constexpr auto TypeVersion = 4;
|
||||
static constexpr auto Preloadable = true;
|
||||
ox::Vector<ox::String> colorNames;
|
||||
ox::Vector<PalettePageV1> pages;
|
||||
};
|
||||
|
||||
OX_MODEL_BEGIN(PaletteV4)
|
||||
OX_MODEL_FIELD(colorNames)
|
||||
OX_MODEL_FIELD(pages)
|
||||
OX_MODEL_END()
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr bool valid(PaletteV4 const&p) noexcept {
|
||||
auto const colors = p.colorNames.size();
|
||||
return ox::all_of(p.pages.begin(), p.pages.end(), [colors](PalettePageV1 const&page) {
|
||||
return page.colors.size() == colors;
|
||||
});
|
||||
}
|
||||
|
||||
constexpr ox::Error repair(PaletteV4 &p) noexcept {
|
||||
auto const colors = p.colorNames.size();
|
||||
for (auto &page : p.pages) {
|
||||
page.colors.resize(colors);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
using Palette = PaletteV4;
|
||||
|
||||
|
||||
struct CompactPaletteV1 {
|
||||
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.CompactPalette";
|
||||
static constexpr auto TypeVersion = 1;
|
||||
static constexpr auto Preloadable = true;
|
||||
ox::Vector<ox::Vector<Color16>> pages{};
|
||||
};
|
||||
|
||||
OX_MODEL_BEGIN(CompactPaletteV1)
|
||||
OX_MODEL_FIELD(pages)
|
||||
OX_MODEL_END()
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr bool valid(CompactPaletteV1 const&p) noexcept {
|
||||
size_t colors{};
|
||||
for (auto const&page : p.pages) {
|
||||
colors = ox::max(colors, page.size());
|
||||
}
|
||||
return ox::all_of(p.pages.begin(), p.pages.end(), [colors](ox::Vector<Color16> const&page) {
|
||||
return page.size() == colors;
|
||||
});
|
||||
}
|
||||
|
||||
constexpr ox::Error repair(CompactPaletteV1 &p) noexcept {
|
||||
size_t colors{};
|
||||
for (auto const&page : p.pages) {
|
||||
colors = ox::max(colors, page.size());
|
||||
}
|
||||
for (auto &page : p.pages) {
|
||||
page.resize(colors);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
using CompactPalette = CompactPaletteV1;
|
||||
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr Color16 color(Palette const&pal, size_t page, size_t idx) noexcept {
|
||||
if (page < pal.pages.size() && idx < pal.pages[page].colors.size()) [[likely]] {
|
||||
return pal.pages[page].colors[idx];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr Color16 color(CompactPalette const&pal, size_t page, size_t idx) noexcept {
|
||||
if (page < pal.pages.size() && idx < pal.pages[page].size()) [[likely]] {
|
||||
return pal.pages[page][idx];
|
||||
}
|
||||
@@ -48,12 +224,44 @@ constexpr Color16 color(Palette const&pal, size_t page, size_t idx) noexcept {
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr Color16 color(Palette const&pal, size_t idx) noexcept {
|
||||
auto constexpr page = 0;
|
||||
return color(pal, page, idx);
|
||||
return color(pal, 0, idx);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr size_t colors(Palette const&pal, size_t page = 0) noexcept {
|
||||
constexpr Color16 color(CompactPalette const&pal, size_t idx) noexcept {
|
||||
return color(pal, 0, idx);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr auto &colors(Palette &pal, size_t page = 0) noexcept {
|
||||
return pal.pages[page].colors;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr auto &colors(CompactPalette &pal, size_t page = 0) noexcept {
|
||||
return pal.pages[page];
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr auto &colors(Palette const&pal, size_t page = 0) noexcept {
|
||||
return pal.pages[page];
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr auto &colors(CompactPalette const&pal, size_t page = 0) noexcept {
|
||||
return pal.pages[page];
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr size_t colorCnt(Palette const&pal, size_t page = 0) noexcept {
|
||||
if (page < pal.pages.size()) [[likely]] {
|
||||
return pal.pages[page].colors.size();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr size_t colorCnt(CompactPalette const&pal, size_t page = 0) noexcept {
|
||||
if (page < pal.pages.size()) [[likely]] {
|
||||
return pal.pages[page].size();
|
||||
}
|
||||
@@ -62,6 +270,15 @@ constexpr size_t colors(Palette const&pal, size_t page = 0) noexcept {
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr size_t largestPage(Palette const&pal) noexcept {
|
||||
size_t out{};
|
||||
for (auto const&page : pal.pages) {
|
||||
out = ox::max(out, page.colors.size());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr size_t largestPage(CompactPalette const&pal) noexcept {
|
||||
size_t out{};
|
||||
for (auto const&page : pal.pages) {
|
||||
out = ox::max(out, page.size());
|
||||
@@ -69,16 +286,4 @@ constexpr size_t largestPage(Palette const&pal) noexcept {
|
||||
return out;
|
||||
}
|
||||
|
||||
oxModelBegin(NostalgiaPalette)
|
||||
oxModelField(colors)
|
||||
oxModelEnd()
|
||||
|
||||
oxModelBegin(PaletteV1)
|
||||
oxModelField(colors)
|
||||
oxModelEnd()
|
||||
|
||||
oxModelBegin(PaletteV2)
|
||||
oxModelField(pages)
|
||||
oxModelEnd()
|
||||
|
||||
}
|
||||
|
||||
@@ -31,6 +31,19 @@ struct TileSheetV1 {
|
||||
ox::Vector<uint8_t> pixels = {};
|
||||
};
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr bool valid(TileSheetV1 const&ts) noexcept {
|
||||
auto const bytes = static_cast<size_t>(ts.columns * ts.rows * PixelsPerTile) / (ts.bpp == 4 ? 2 : 1);
|
||||
return (ts.bpp == 4 || ts.bpp == 8) && ts.pixels.size() == bytes;
|
||||
}
|
||||
|
||||
constexpr ox::Error repair(TileSheetV1 &ts, int bpp) noexcept {
|
||||
auto const bytes = static_cast<size_t>(ts.columns * ts.rows * PixelsPerTile) / (bpp == 4 ? 2 : 1);
|
||||
ts.pixels.resize(bytes);
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
struct TileSheetV2 {
|
||||
using SubSheetIdx = ox::Vector<std::size_t, 4>;
|
||||
|
||||
@@ -43,8 +56,8 @@ struct TileSheetV2 {
|
||||
ox::Vector<SubSheet> subsheets;
|
||||
ox::Vector<uint8_t> pixels;
|
||||
constexpr SubSheet() noexcept = default;
|
||||
constexpr SubSheet(ox::CRStringView pName, int pColumns, int pRows, int bpp) noexcept:
|
||||
name(pName),
|
||||
constexpr SubSheet(ox::StringParam pName, int pColumns, int pRows, int bpp) noexcept:
|
||||
name(std::move(pName)),
|
||||
columns(pColumns),
|
||||
rows(pRows),
|
||||
pixels(static_cast<size_t>(columns * rows * PixelsPerTile) / (bpp == 4 ? 2u : 1u)) {
|
||||
@@ -59,6 +72,37 @@ struct TileSheetV2 {
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr bool valid(TileSheetV2::SubSheet const&ss, int bpp) noexcept {
|
||||
auto const bytes = static_cast<size_t>(ss.columns * ss.rows * PixelsPerTile) / (bpp == 4 ? 2 : 1);
|
||||
return ox::all_of(ss.subsheets.begin(), ss.subsheets.end(),
|
||||
[bpp, bytes](TileSheetV2::SubSheet const&s) {
|
||||
return bytes == s.pixels.size() && valid(s, bpp);
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr bool valid(TileSheetV2 const&ts) noexcept {
|
||||
return (ts.bpp == 4 || ts.bpp == 8) && valid(ts.subsheet, ts.bpp);
|
||||
}
|
||||
|
||||
constexpr void repair(TileSheetV2::SubSheet &ss, int bpp) noexcept {
|
||||
auto const bytes = static_cast<size_t>(ss.columns * ss.rows * PixelsPerTile) / (bpp == 4 ? 2 : 1);
|
||||
ss.pixels.resize(bytes);
|
||||
for (auto &s : ss.subsheets) {
|
||||
repair(s, bpp);
|
||||
}
|
||||
}
|
||||
|
||||
constexpr ox::Error repair(TileSheetV2 &ts) noexcept {
|
||||
if (ts.bpp != 4 && ts.bpp != 8) {
|
||||
return ox::Error(1, "Unable to repair TileSheet");
|
||||
}
|
||||
repair(ts.subsheet, ts.bpp);
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
using SubSheetId = int32_t;
|
||||
|
||||
struct TileSheetV3 {
|
||||
@@ -74,14 +118,14 @@ struct TileSheetV3 {
|
||||
ox::Vector<SubSheet> subsheets;
|
||||
ox::Vector<uint8_t> pixels;
|
||||
constexpr SubSheet() noexcept = default;
|
||||
inline SubSheet(
|
||||
SubSheet(
|
||||
SubSheetId pId,
|
||||
ox::CRStringView pName,
|
||||
ox::StringParam pName,
|
||||
int pColumns,
|
||||
int pRows,
|
||||
int bpp) noexcept:
|
||||
id(pId),
|
||||
name(pName),
|
||||
name(std::move(pName)),
|
||||
columns(pColumns),
|
||||
rows(pRows),
|
||||
pixels(static_cast<std::size_t>(columns * rows * PixelsPerTile) / (bpp == 4 ? 2u : 1u)) {
|
||||
@@ -98,7 +142,38 @@ struct TileSheetV3 {
|
||||
|
||||
};
|
||||
|
||||
struct TileSheet {
|
||||
[[nodiscard]]
|
||||
constexpr bool valid(TileSheetV3::SubSheet const&ss, int bpp) noexcept {
|
||||
auto const bytes = static_cast<size_t>(ss.columns * ss.rows * PixelsPerTile) / (bpp == 4 ? 2 : 1);
|
||||
return ox::all_of(ss.subsheets.begin(), ss.subsheets.end(),
|
||||
[bpp, bytes](TileSheetV3::SubSheet const&s) {
|
||||
return bytes == s.pixels.size() && valid(s, bpp);
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr bool valid(TileSheetV3 const&ts) noexcept {
|
||||
return (ts.bpp == 4 || ts.bpp == 8) && valid(ts.subsheet, ts.bpp);
|
||||
}
|
||||
|
||||
constexpr void repair(TileSheetV3::SubSheet &ss, int bpp) noexcept {
|
||||
auto const bytes = static_cast<size_t>(ss.columns * ss.rows * PixelsPerTile) / (bpp == 4 ? 2 : 1);
|
||||
ss.pixels.resize(bytes);
|
||||
for (auto &s : ss.subsheets) {
|
||||
repair(s, bpp);
|
||||
}
|
||||
}
|
||||
|
||||
constexpr ox::Error repair(TileSheetV3 &ts) noexcept {
|
||||
if (ts.bpp != 4 && ts.bpp != 8) {
|
||||
return ox::Error(1, "Unable to repair TileSheet");
|
||||
}
|
||||
repair(ts.subsheet, ts.bpp);
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
struct TileSheetV4 {
|
||||
using SubSheetIdx = ox::Vector<std::size_t, 4>;
|
||||
|
||||
struct SubSheet {
|
||||
@@ -112,26 +187,26 @@ struct TileSheet {
|
||||
ox::Vector<uint8_t> pixels;
|
||||
|
||||
constexpr SubSheet() noexcept = default;
|
||||
inline SubSheet(
|
||||
SubSheet(
|
||||
SubSheetId pId,
|
||||
ox::CRStringView pName,
|
||||
ox::StringParam pName,
|
||||
int pColumns,
|
||||
int pRows,
|
||||
int bpp) noexcept:
|
||||
id(pId),
|
||||
name(pName),
|
||||
name(std::move(pName)),
|
||||
columns(pColumns),
|
||||
rows(pRows),
|
||||
pixels(static_cast<std::size_t>(columns * rows * PixelsPerTile) / (bpp == 4 ? 2u : 1u)) {
|
||||
}
|
||||
inline SubSheet(
|
||||
SubSheet(
|
||||
SubSheetId pId,
|
||||
ox::CRStringView pName,
|
||||
ox::StringParam pName,
|
||||
int pColumns,
|
||||
int pRows,
|
||||
ox::Vector<uint8_t> pPixels) noexcept:
|
||||
id(pId),
|
||||
name(pName),
|
||||
name(std::move(pName)),
|
||||
columns(pColumns),
|
||||
rows(pRows),
|
||||
pixels(std::move(pPixels)) {
|
||||
@@ -151,10 +226,43 @@ struct TileSheet {
|
||||
ox::FileAddress defaultPalette;
|
||||
SubSheet subsheet{0, "Root", 1, 1, bpp};
|
||||
|
||||
constexpr TileSheet() noexcept = default;
|
||||
constexpr TileSheetV4() noexcept = default;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr bool valid(TileSheetV4::SubSheet const&ss, int bpp) noexcept {
|
||||
auto const bytes = static_cast<size_t>(ss.columns * ss.rows * PixelsPerTile) / (bpp == 4 ? 2 : 1);
|
||||
return ox::all_of(ss.subsheets.begin(), ss.subsheets.end(),
|
||||
[bpp, bytes](TileSheetV4::SubSheet const&s) {
|
||||
return bytes == s.pixels.size() && valid(s, bpp);
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr bool valid(TileSheetV4 const&ts) noexcept {
|
||||
return (ts.bpp == 4 || ts.bpp == 8) && valid(ts.subsheet, ts.bpp);
|
||||
}
|
||||
|
||||
constexpr void repair(TileSheetV4::SubSheet &ss, int bpp) noexcept {
|
||||
auto const bytes = static_cast<size_t>(ss.columns * ss.rows * PixelsPerTile) / (bpp == 4 ? 2 : 1);
|
||||
ss.pixels.resize(bytes);
|
||||
for (auto &s : ss.subsheets) {
|
||||
repair(s, bpp);
|
||||
}
|
||||
}
|
||||
|
||||
constexpr ox::Error repair(TileSheetV4 &ts) noexcept {
|
||||
if (ts.bpp != 4 && ts.bpp != 8) {
|
||||
return ox::Error(1, "Unable to repair TileSheet");
|
||||
}
|
||||
repair(ts.subsheet, ts.bpp);
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
using TileSheet = TileSheetV4;
|
||||
|
||||
[[nodiscard]]
|
||||
std::size_t idx(TileSheet::SubSheet const&ss, ox::Point const&pt) noexcept;
|
||||
|
||||
@@ -222,6 +330,14 @@ ox::Error setPixelCount(TileSheet::SubSheet &ss, int8_t pBpp, std::size_t cnt) n
|
||||
[[nodiscard]]
|
||||
unsigned pixelCnt(TileSheet::SubSheet const&ss, int8_t pBpp) noexcept;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param ss
|
||||
* @param pBpp
|
||||
* @param sz size of Subsheet in tiles (not pixels)
|
||||
*/
|
||||
ox::Error resizeSubsheet(TileSheet::SubSheet &ss, int8_t pBpp, ox::Size const&sz) noexcept;
|
||||
|
||||
/**
|
||||
* validateSubSheetIdx takes a SubSheetIdx and moves the index to the
|
||||
* preceding or parent sheet if the current corresponding sheet does
|
||||
@@ -230,7 +346,7 @@ unsigned pixelCnt(TileSheet::SubSheet const&ss, int8_t pBpp) noexcept;
|
||||
* @return a valid version of idx
|
||||
*/
|
||||
[[nodiscard]]
|
||||
TileSheet::SubSheetIdx validateSubSheetIdx(TileSheet const&ts, TileSheet::SubSheetIdx const&idx) noexcept;
|
||||
TileSheet::SubSheetIdx validateSubSheetIdx(TileSheet const&ts, TileSheet::SubSheetIdx idx) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
TileSheet::SubSheet const&getSubSheet(
|
||||
@@ -272,86 +388,114 @@ uint8_t getPixel8Bpp(
|
||||
ox::Point const&pt,
|
||||
TileSheet::SubSheetIdx const&subsheetIdx) noexcept;
|
||||
|
||||
ox::Result<SubSheetId> getIdFor(TileSheet const&ts, ox::CRStringView path) noexcept;
|
||||
ox::Result<SubSheetId> getIdFor(TileSheet const&ts, ox::StringViewCR path) noexcept;
|
||||
|
||||
ox::Result<unsigned> getTileOffset(TileSheet const&ts, ox::CRStringView pNamePath) noexcept;
|
||||
ox::Result<unsigned> getTileOffset(TileSheet const&ts, ox::StringViewCR pNamePath) noexcept;
|
||||
|
||||
ox::Result<ox::StringView> getNameFor(TileSheet::SubSheet const&ss, SubSheetId pId) noexcept;
|
||||
|
||||
ox::Result<ox::StringView> getNameFor(TileSheet const&ss, SubSheetId pId) noexcept;
|
||||
ox::Result<ox::StringView> getNameFor(TileSheet const&ts, SubSheetId pId) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
ox::Vector<uint8_t> pixels(TileSheet &ts) noexcept;
|
||||
|
||||
using TileSheetV4 = TileSheet;
|
||||
|
||||
struct CompactTileSheet {
|
||||
struct CompactTileSheetV1 {
|
||||
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.CompactTileSheet";
|
||||
static constexpr auto TypeVersion = 1;
|
||||
static constexpr auto Preloadable = true;
|
||||
int8_t bpp = 0;
|
||||
ox::FileAddress defaultPalette;
|
||||
ox::Vector<uint8_t> pixels = {};
|
||||
ox::Vector<uint8_t> pixels;
|
||||
};
|
||||
|
||||
oxModelBegin(TileSheetV1)
|
||||
oxModelField(bpp)
|
||||
oxModelField(rows)
|
||||
oxModelField(columns)
|
||||
oxModelField(defaultPalette)
|
||||
oxModelField(pal)
|
||||
oxModelField(pixels)
|
||||
oxModelEnd()
|
||||
[[nodiscard]]
|
||||
constexpr bool valid(CompactTileSheetV1 const&ts) noexcept {
|
||||
return ts.bpp == 4 || ts.bpp == 8;
|
||||
}
|
||||
|
||||
oxModelBegin(TileSheetV2::SubSheet)
|
||||
oxModelField(name)
|
||||
oxModelField(rows)
|
||||
oxModelField(columns)
|
||||
oxModelField(subsheets)
|
||||
oxModelField(pixels)
|
||||
oxModelEnd()
|
||||
|
||||
oxModelBegin(TileSheetV2)
|
||||
oxModelField(bpp)
|
||||
oxModelField(defaultPalette)
|
||||
oxModelField(subsheet)
|
||||
oxModelEnd()
|
||||
using CompactTileSheet = CompactTileSheetV1;
|
||||
|
||||
oxModelBegin(TileSheetV3::SubSheet)
|
||||
oxModelField(name)
|
||||
oxModelField(rows)
|
||||
oxModelField(columns)
|
||||
oxModelField(subsheets)
|
||||
oxModelField(pixels)
|
||||
oxModelEnd()
|
||||
[[nodiscard]]
|
||||
uint8_t getPixel4Bpp(
|
||||
CompactTileSheet const&ts,
|
||||
size_t idx) noexcept;
|
||||
|
||||
oxModelBegin(TileSheetV3)
|
||||
oxModelField(bpp)
|
||||
oxModelField(idIt)
|
||||
oxModelField(defaultPalette)
|
||||
oxModelField(subsheet)
|
||||
oxModelEnd()
|
||||
[[nodiscard]]
|
||||
uint8_t getPixel8Bpp(
|
||||
CompactTileSheet const&ts,
|
||||
size_t idx) noexcept;
|
||||
|
||||
oxModelBegin(TileSheetV4::SubSheet)
|
||||
oxModelField(id)
|
||||
oxModelField(name)
|
||||
oxModelField(rows)
|
||||
oxModelField(columns)
|
||||
oxModelField(subsheets)
|
||||
oxModelField(pixels)
|
||||
oxModelEnd()
|
||||
[[nodiscard]]
|
||||
ox::Pair<uint8_t> get2Pixels4Bpp(
|
||||
CompactTileSheet const&ts,
|
||||
size_t idx) noexcept;
|
||||
|
||||
oxModelBegin(TileSheetV4)
|
||||
oxModelField(bpp)
|
||||
oxModelField(idIt)
|
||||
oxModelField(defaultPalette)
|
||||
oxModelField(subsheet)
|
||||
oxModelEnd()
|
||||
[[nodiscard]]
|
||||
ox::Pair<uint8_t> get2Pixels8Bpp(
|
||||
CompactTileSheet const&ts,
|
||||
size_t idx) noexcept;
|
||||
|
||||
oxModelBegin(CompactTileSheet)
|
||||
oxModelField(bpp)
|
||||
oxModelField(defaultPalette)
|
||||
oxModelField(pixels)
|
||||
oxModelEnd()
|
||||
OX_MODEL_BEGIN(TileSheetV1)
|
||||
OX_MODEL_FIELD(bpp)
|
||||
OX_MODEL_FIELD(rows)
|
||||
OX_MODEL_FIELD(columns)
|
||||
OX_MODEL_FIELD(defaultPalette)
|
||||
OX_MODEL_FIELD(pal)
|
||||
OX_MODEL_FIELD(pixels)
|
||||
OX_MODEL_END()
|
||||
|
||||
OX_MODEL_BEGIN(TileSheetV2::SubSheet)
|
||||
OX_MODEL_FIELD(name)
|
||||
OX_MODEL_FIELD(rows)
|
||||
OX_MODEL_FIELD(columns)
|
||||
OX_MODEL_FIELD(subsheets)
|
||||
OX_MODEL_FIELD(pixels)
|
||||
OX_MODEL_END()
|
||||
|
||||
OX_MODEL_BEGIN(TileSheetV2)
|
||||
OX_MODEL_FIELD(bpp)
|
||||
OX_MODEL_FIELD(defaultPalette)
|
||||
OX_MODEL_FIELD(subsheet)
|
||||
OX_MODEL_END()
|
||||
|
||||
OX_MODEL_BEGIN(TileSheetV3::SubSheet)
|
||||
OX_MODEL_FIELD(name)
|
||||
OX_MODEL_FIELD(rows)
|
||||
OX_MODEL_FIELD(columns)
|
||||
OX_MODEL_FIELD(subsheets)
|
||||
OX_MODEL_FIELD(pixels)
|
||||
OX_MODEL_END()
|
||||
|
||||
OX_MODEL_BEGIN(TileSheetV3)
|
||||
OX_MODEL_FIELD(bpp)
|
||||
OX_MODEL_FIELD(idIt)
|
||||
OX_MODEL_FIELD(defaultPalette)
|
||||
OX_MODEL_FIELD(subsheet)
|
||||
OX_MODEL_END()
|
||||
|
||||
OX_MODEL_BEGIN(TileSheetV4::SubSheet)
|
||||
OX_MODEL_FIELD(id)
|
||||
OX_MODEL_FIELD(name)
|
||||
OX_MODEL_FIELD(rows)
|
||||
OX_MODEL_FIELD(columns)
|
||||
OX_MODEL_FIELD(subsheets)
|
||||
OX_MODEL_FIELD(pixels)
|
||||
OX_MODEL_END()
|
||||
|
||||
OX_MODEL_BEGIN(TileSheetV4)
|
||||
OX_MODEL_FIELD(bpp)
|
||||
OX_MODEL_FIELD(idIt)
|
||||
OX_MODEL_FIELD(defaultPalette)
|
||||
OX_MODEL_FIELD(subsheet)
|
||||
OX_MODEL_END()
|
||||
|
||||
OX_MODEL_BEGIN(CompactTileSheetV1)
|
||||
OX_MODEL_FIELD(bpp)
|
||||
OX_MODEL_FIELD(defaultPalette)
|
||||
OX_MODEL_FIELD(pixels)
|
||||
OX_MODEL_END()
|
||||
|
||||
ox::Vector<uint32_t> resizeTileSheetData(
|
||||
ox::Vector<uint32_t> const&srcPixels,
|
||||
|
||||
@@ -21,7 +21,7 @@ ox::Error initGfx(Context &ctx, InitParams const&) noexcept;
|
||||
|
||||
ox::Result<ContextUPtr> init(turbine::Context &tctx, InitParams const¶ms) noexcept {
|
||||
auto ctx = ox::make_unique<Context>(tctx);
|
||||
oxReturnError(initGfx(*ctx, params));
|
||||
OX_RETURN_ERROR(initGfx(*ctx, params));
|
||||
return ContextUPtr(std::move(ctx));
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
*/
|
||||
|
||||
#include <ox/fs/fs.hpp>
|
||||
#include <ox/mc/mc.hpp>
|
||||
#include <ox/std/array.hpp>
|
||||
|
||||
#include <teagba/addresses.hpp>
|
||||
@@ -11,118 +10,18 @@
|
||||
#include <teagba/registers.hpp>
|
||||
|
||||
#include <keel/keel.hpp>
|
||||
#include <turbine/turbine.hpp>
|
||||
|
||||
#include <nostalgia/core/color.hpp>
|
||||
#include <nostalgia/core/context.hpp>
|
||||
#include <nostalgia/core/gfx.hpp>
|
||||
#include <nostalgia/core/tilesheet.hpp>
|
||||
|
||||
#include "context.hpp"
|
||||
|
||||
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
constexpr auto GbaTileColumns = 32;
|
||||
constexpr auto GbaTileRows = 32;
|
||||
constexpr auto SpriteCount = 128;
|
||||
|
||||
struct GbaTileMapTarget {
|
||||
static constexpr auto TypeName = CompactTileSheet::TypeName;
|
||||
static constexpr auto TypeVersion = CompactTileSheet::TypeVersion;
|
||||
unsigned &bpp;
|
||||
ox::FileAddress defaultPalette;
|
||||
volatile uint16_t *tileMap = nullptr;
|
||||
// the following values are not actually in CompactTileSheet,
|
||||
// and only exist to communicate with the loading process
|
||||
size_t tileWriteIdx = 0;
|
||||
unsigned targetBpp = 0;
|
||||
TileSheetSetEntry const*setEntry = nullptr;
|
||||
};
|
||||
|
||||
[[nodiscard]]
|
||||
static bool loadPixel(TileSheetSetEntry const&setEntry, size_t §ionIdx, int tileIdx) noexcept {
|
||||
if (setEntry.sections.size() <= sectionIdx) {
|
||||
return false;
|
||||
}
|
||||
auto §ion = setEntry.sections[sectionIdx];
|
||||
if (tileIdx < section.begin) {
|
||||
return false;
|
||||
}
|
||||
if (tileIdx > section.end()) {
|
||||
if (sectionIdx >= setEntry.sections.size()) {
|
||||
return false;
|
||||
}
|
||||
++sectionIdx;
|
||||
return tileIdx > section.begin && tileIdx <= section.end();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
constexpr ox::Error model(auto *io, ox::CommonPtrWith<GbaTileMapTarget> auto *t) noexcept {
|
||||
oxReturnError(io->template setTypeInfo<CompactTileSheet>());
|
||||
oxReturnError(io->field("bpp", &t->bpp));
|
||||
oxReturnError(io->field("defaultPalette", &t->defaultPalette));
|
||||
if (t->targetBpp == 0) {
|
||||
t->targetBpp = t->bpp;
|
||||
}
|
||||
if (t->targetBpp != t->bpp && t->bpp == 8) {
|
||||
return OxError(1, "Cannot load an 8 BPP tilesheet into a 4 BPP CBB");
|
||||
}
|
||||
ox::Error out{};
|
||||
if (t->setEntry) {
|
||||
// The following code is atrocious, but it works.
|
||||
// It might be possible to clean it up a little, but it probably
|
||||
// cannot be seriously optimized without preloading TileSheets.
|
||||
size_t sectionIdx = 0;
|
||||
if (t->targetBpp == t->bpp) {
|
||||
uint16_t intermediate = 0;
|
||||
size_t const fourBpp = t->bpp == 4;
|
||||
const auto handleTileMap = [t, &intermediate, §ionIdx, fourBpp]
|
||||
(std::size_t i, uint8_t const*tile) {
|
||||
auto const tileIdx = static_cast<int>((i * (2 * fourBpp)) / PixelsPerTile);
|
||||
if (!loadPixel(*t->setEntry, sectionIdx, tileIdx)) {
|
||||
return ox::Error{};
|
||||
}
|
||||
if (i & 1) { // i is odd
|
||||
intermediate |= static_cast<uint16_t>(*tile) << 8;
|
||||
t->tileMap[t->tileWriteIdx] = intermediate;
|
||||
++t->tileWriteIdx;
|
||||
} else { // i is even
|
||||
intermediate = *tile & 0x00ff;
|
||||
}
|
||||
return ox::Error{};
|
||||
};
|
||||
out = io->template field<uint8_t, decltype(handleTileMap)>("tileMap", handleTileMap);
|
||||
} else if (t->targetBpp > t->bpp) { // 4 -> 8 bits
|
||||
const auto handleTileMap = [t, §ionIdx](std::size_t i, uint8_t const*tile) {
|
||||
auto constexpr BytesPerTile4Bpp = 32;
|
||||
auto const tileIdx = static_cast<int>(i / BytesPerTile4Bpp);
|
||||
if (!loadPixel(*t->setEntry, sectionIdx, tileIdx)) {
|
||||
return ox::Error{};
|
||||
}
|
||||
uint16_t const px1 = *tile & 0xf;
|
||||
uint16_t const px2 = *tile >> 4;
|
||||
t->tileMap[t->tileWriteIdx] = static_cast<uint16_t>(px1 | (px2 << 8));
|
||||
++t->tileWriteIdx;
|
||||
return ox::Error{};
|
||||
};
|
||||
out = io->template field<uint8_t, decltype(handleTileMap)>("tileMap", handleTileMap);
|
||||
}
|
||||
} else {
|
||||
uint16_t intermediate = 0;
|
||||
const auto handleTileMap = [t, &intermediate](std::size_t i, const uint8_t*tile) {
|
||||
if (i & 1) { // i is odd
|
||||
intermediate |= static_cast<uint16_t>(*tile) << 8;
|
||||
t->tileMap[i / 2] = intermediate;
|
||||
} else { // i is even
|
||||
intermediate = *tile & 0x00ff;
|
||||
}
|
||||
return ox::Error{};
|
||||
};
|
||||
out = io->template field<uint8_t, decltype(handleTileMap)>("tileMap", handleTileMap);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
static constexpr auto SpriteCount = 128;
|
||||
|
||||
ox::Error initGfx(Context&, InitParams const&) noexcept {
|
||||
for (auto bgCtl = ®_BG0CTL; bgCtl <= ®_BG3CTL; bgCtl += 2) {
|
||||
@@ -138,13 +37,13 @@ ox::Error initGfx(Context&, InitParams const&) noexcept {
|
||||
ox::Error loadBgPalette(
|
||||
Context&,
|
||||
size_t palBank,
|
||||
Palette const&palette,
|
||||
CompactPalette const&palette,
|
||||
size_t page) noexcept {
|
||||
if (palette.pages.empty()) {
|
||||
return {};
|
||||
}
|
||||
auto const paletteMem = MEM_BG_PALETTE + palBank * 16;
|
||||
for (auto i = 0u; i < colors(palette, page); ++i) {
|
||||
for (auto i = 0u; i < colorCnt(palette, page); ++i) {
|
||||
paletteMem[i] = color(palette, page, i);
|
||||
}
|
||||
return {};
|
||||
@@ -152,89 +51,104 @@ ox::Error loadBgPalette(
|
||||
|
||||
ox::Error loadSpritePalette(
|
||||
Context&,
|
||||
Palette const&palette,
|
||||
CompactPalette const&palette,
|
||||
size_t page) noexcept {
|
||||
if (palette.pages.empty()) {
|
||||
return {};
|
||||
}
|
||||
auto const paletteMem = MEM_SPRITE_PALETTE;
|
||||
for (auto i = 0u; i < colors(palette, page); ++i) {
|
||||
for (auto i = 0u; i < colorCnt(palette, page); ++i) {
|
||||
paletteMem[i] = color(palette, page, i);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Error loadBgPalette(
|
||||
Context &ctx,
|
||||
size_t palBank,
|
||||
ox::FileAddress const&paletteAddr) noexcept {
|
||||
oxRequire(pal, keel::readObj<Palette>(keelCtx(ctx), paletteAddr));
|
||||
return loadBgPalette(ctx, palBank, *pal, 0);
|
||||
}
|
||||
|
||||
ox::Error loadSpritePalette(
|
||||
Context &ctx,
|
||||
ox::FileAddress const&paletteAddr) noexcept {
|
||||
oxRequire(pal, keel::readObj<Palette>(keelCtx(ctx), paletteAddr));
|
||||
return loadSpritePalette(ctx, *pal, 0);
|
||||
}
|
||||
|
||||
static ox::Error loadTileSheetSet(
|
||||
Context &ctx,
|
||||
uint16_t *tileMapTargetMem,
|
||||
ox::Span<uint16_t> tileMapTargetMem,
|
||||
TileSheetSet const&set) noexcept {
|
||||
auto &rom = ctx.rom();
|
||||
size_t tileWriteIdx = 0;
|
||||
size_t const bppMod = set.bpp == 4;
|
||||
for (auto const&entry : set.entries) {
|
||||
oxRequire(tsStat, rom.stat(entry.tilesheet));
|
||||
oxRequire(ts, rom.directAccess(entry.tilesheet));
|
||||
unsigned tilesheetBpp{};
|
||||
GbaTileMapTarget target{
|
||||
.bpp = tilesheetBpp,
|
||||
.defaultPalette = {},
|
||||
.tileMap = tileMapTargetMem + tileWriteIdx,
|
||||
.targetBpp = static_cast<unsigned>(set.bpp),
|
||||
.setEntry = &entry,
|
||||
};
|
||||
oxReturnError(ox::readMC({ts, static_cast<std::size_t>(tsStat.size)}, target));
|
||||
tileWriteIdx += target.tileWriteIdx;
|
||||
OX_REQUIRE(ts, keel::readObj<CompactTileSheet>(keelCtx(ctx), entry.tilesheet));
|
||||
if (set.bpp != ts->bpp && ts->bpp == 8) {
|
||||
return ox::Error(1, "cannot load an 8 BPP tilesheet into a 4 BPP CBB");
|
||||
}
|
||||
for (auto const&s : entry.sections) {
|
||||
auto const cnt = (static_cast<size_t>(s.tiles) * PixelsPerTile) >> bppMod;
|
||||
for (size_t i = 0; i < cnt; ++i) {
|
||||
auto const begin = static_cast<size_t>(s.begin) * (PixelsPerTile >> bppMod);
|
||||
auto const srcIdx = begin + i * 2;
|
||||
auto const v = static_cast<uint16_t>(
|
||||
static_cast<uint16_t>(ts->pixels[srcIdx]) |
|
||||
(static_cast<uint16_t>(ts->pixels[srcIdx + 1]) << 8));
|
||||
tileMapTargetMem[tileWriteIdx + i] = v;
|
||||
}
|
||||
tileWriteIdx += cnt >> bppMod;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Error loadBgTileSheet(
|
||||
Context &ctx,
|
||||
unsigned cbb,
|
||||
ox::FileAddress const&tilesheetAddr,
|
||||
ox::Optional<unsigned> const&paletteBank) noexcept {
|
||||
auto &rom = ctx.rom();
|
||||
oxRequire(tsStat, rom.stat(tilesheetAddr));
|
||||
oxRequire(ts, rom.directAccess(tilesheetAddr));
|
||||
GbaTileMapTarget target{
|
||||
.bpp = ctx.cbbData[cbb].bpp,
|
||||
.defaultPalette = {},
|
||||
.tileMap = MEM_BG_TILES[cbb].data(),
|
||||
};
|
||||
oxReturnError(ox::readMC({ts, static_cast<std::size_t>(tsStat.size)}, target));
|
||||
unsigned const cbb,
|
||||
CompactTileSheet const&ts,
|
||||
size_t const dstTileIdx,
|
||||
size_t const srcTileIdx,
|
||||
size_t const tileCnt) noexcept {
|
||||
size_t const bppMod = ts.bpp == 4;
|
||||
size_t const bytesPerTile = PixelsPerTile >> bppMod;
|
||||
auto const pixCnt = tileCnt * bytesPerTile;
|
||||
auto const srcPxIdx = srcTileIdx * bytesPerTile;
|
||||
auto const dstPxIdx = (dstTileIdx * bytesPerTile) / 2;
|
||||
for (size_t i = 0; i < pixCnt; ++i) {
|
||||
auto const srcIdx = srcPxIdx + i * 2;
|
||||
auto const p1 = static_cast<uint16_t>(ts.pixels[srcIdx]);
|
||||
auto const p2 = static_cast<uint16_t>(ts.pixels[srcIdx + 1]);
|
||||
MEM_BG_TILES[cbb][dstPxIdx + i] = static_cast<uint16_t>(p1 | (p2 << 8));
|
||||
}
|
||||
// update bpp of all bgs with the updated cbb
|
||||
const auto bpp = ctx.cbbData[cbb].bpp;
|
||||
auto const bpp = ctx.cbbData[cbb].bpp;
|
||||
teagba::iterateBgCtl([bpp, cbb](volatile BgCtl &bgCtl) {
|
||||
if (teagba::bgCbb(bgCtl) == cbb) {
|
||||
teagba::bgSetBpp(bgCtl, bpp);
|
||||
}
|
||||
});
|
||||
if (paletteBank.has_value() && target.defaultPalette) {
|
||||
oxReturnError(loadBgPalette(ctx, *paletteBank, target.defaultPalette));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Error loadBgTileSheet(
|
||||
Context &ctx,
|
||||
unsigned cbb,
|
||||
CompactTileSheet const&ts,
|
||||
ox::Optional<unsigned> const&paletteBank) noexcept {
|
||||
auto const cnt = (ts.pixels.size() * PixelsPerTile) / (1 + (ts.bpp == 4));
|
||||
for (size_t i = 0; i < cnt; ++i) {
|
||||
auto const srcIdx = i * 2;
|
||||
auto const p1 = static_cast<uint16_t>(ts.pixels[srcIdx]);
|
||||
auto const p2 = static_cast<uint16_t>(ts.pixels[srcIdx + 1]);
|
||||
MEM_BG_TILES[cbb][i] = static_cast<uint16_t>(p1 | (p2 << 8));
|
||||
}
|
||||
// update bpp of all bgs with the updated cbb
|
||||
auto const bpp = ctx.cbbData[cbb].bpp;
|
||||
teagba::iterateBgCtl([bpp, cbb](volatile BgCtl &bgCtl) {
|
||||
if (teagba::bgCbb(bgCtl) == cbb) {
|
||||
teagba::bgSetBpp(bgCtl, bpp);
|
||||
}
|
||||
});
|
||||
if (paletteBank.has_value() && ts.defaultPalette) {
|
||||
OX_RETURN_ERROR(loadBgPalette(ctx, *paletteBank, ts.defaultPalette));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Error loadBgTileSheet(
|
||||
Context &ctx,
|
||||
unsigned const cbb,
|
||||
TileSheetSet const&set) noexcept {
|
||||
auto const bpp = static_cast<unsigned>(set.bpp);
|
||||
oxReturnError(loadTileSheetSet(ctx, MEM_BG_TILES[cbb].data(), set));
|
||||
OX_RETURN_ERROR(loadTileSheetSet(ctx, MEM_BG_TILES[cbb], set));
|
||||
// update bpp of all bgs with the updated cbb
|
||||
ctx.cbbData[cbb].bpp = bpp;
|
||||
teagba::iterateBgCtl([bpp, cbb](volatile BgCtl &bgCtl) {
|
||||
@@ -256,22 +170,17 @@ static void setSpritesBpp(unsigned const bpp) noexcept {
|
||||
|
||||
ox::Error loadSpriteTileSheet(
|
||||
Context &ctx,
|
||||
ox::FileAddress const&tilesheetAddr,
|
||||
CompactTileSheet const&ts,
|
||||
bool loadDefaultPalette) noexcept {
|
||||
auto &rom = ctx.rom();
|
||||
oxRequire(tsStat, rom.stat(tilesheetAddr));
|
||||
oxRequire(ts, rom.directAccess(tilesheetAddr));
|
||||
unsigned bpp{};
|
||||
GbaTileMapTarget target{
|
||||
.bpp = bpp,
|
||||
.defaultPalette = {},
|
||||
.tileMap = MEM_SPRITE_TILES,
|
||||
};
|
||||
oxReturnError(ox::readMC({ts, static_cast<std::size_t>(tsStat.size)}, target));
|
||||
if (loadDefaultPalette && target.defaultPalette) {
|
||||
oxReturnError(loadSpritePalette(ctx, target.defaultPalette));
|
||||
for (size_t i = 0; i < ts.pixels.size(); i += 2) {
|
||||
uint16_t v = ts.pixels[i];
|
||||
v |= static_cast<uint16_t>(ts.pixels[i + 1] << 8);
|
||||
MEM_SPRITE_TILES[i] = v;
|
||||
}
|
||||
setSpritesBpp(bpp);
|
||||
if (loadDefaultPalette && ts.defaultPalette) {
|
||||
OX_RETURN_ERROR(loadSpritePalette(ctx, ts.defaultPalette));
|
||||
}
|
||||
setSpritesBpp(static_cast<unsigned>(ts.bpp));
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -279,13 +188,13 @@ ox::Error loadSpriteTileSheet(
|
||||
Context &ctx,
|
||||
TileSheetSet const&set) noexcept {
|
||||
auto const bpp = static_cast<unsigned>(set.bpp);
|
||||
oxReturnError(loadTileSheetSet(ctx, MEM_SPRITE_TILES, set));
|
||||
OX_RETURN_ERROR(loadTileSheetSet(ctx, {MEM_SPRITE_TILES, 32 * ox::units::KB}, set));
|
||||
setSpritesBpp(bpp);
|
||||
return {};
|
||||
}
|
||||
|
||||
void setBgTile(Context&, uint_t bgIdx, int column, int row, BgTile const&tile) noexcept {
|
||||
auto const tileIdx = static_cast<std::size_t>(row * GbaTileColumns + column);
|
||||
void setBgTile(Context &ctx, uint_t bgIdx, int column, int row, BgTile const&tile) noexcept {
|
||||
auto const tileIdx = static_cast<std::size_t>(row * tileColumns(ctx) + column);
|
||||
// see Tonc 9.3
|
||||
MEM_BG_MAP[bgIdx][tileIdx] =
|
||||
static_cast<uint16_t>(tile.tileIdx & 0b1'1111'1111) |
|
||||
@@ -294,8 +203,8 @@ void setBgTile(Context&, uint_t bgIdx, int column, int row, BgTile const&tile) n
|
||||
static_cast<uint16_t>(tile.palBank << 0xc);
|
||||
}
|
||||
|
||||
void clearBg(Context&, uint_t bgIdx) noexcept {
|
||||
memset(MEM_BG_MAP[bgIdx].data(), 0, GbaTileRows * GbaTileColumns);
|
||||
void clearBg(Context &ctx, uint_t bgIdx) noexcept {
|
||||
memset(MEM_BG_MAP[bgIdx].data(), 0, static_cast<size_t>(tileRows(ctx) * tileColumns(ctx)));
|
||||
}
|
||||
|
||||
uint8_t bgStatus(Context&) noexcept {
|
||||
@@ -376,3 +285,5 @@ uint_t spriteCount(Context&) noexcept {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
OX_ALLOW_UNSAFE_BUFFERS_END
|
||||
|
||||
@@ -25,7 +25,9 @@ using namespace nostalgia::core;
|
||||
|
||||
void panic(const char *file, int line, const char *panicMsg, ox::Error const&err) noexcept {
|
||||
// reset heap to make sure we have enough memory to allocate context data
|
||||
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
|
||||
ox::heapmgr::initHeap(HEAP_BEGIN, HEAP_END);
|
||||
OX_ALLOW_UNSAFE_BUFFERS_END
|
||||
auto tctx = turbine::init(keel::loadRomFs("").unwrap(), "Nostalgia").unwrap();
|
||||
auto ctx = init(*tctx).unwrap();
|
||||
std::ignore = initGfx(*ctx, {});
|
||||
|
||||
@@ -2,10 +2,108 @@
|
||||
* Copyright 2016 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <keel/media.hpp>
|
||||
#include <nostalgia/core/gfx.hpp>
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
constexpr auto GbaTileColumns = 32;
|
||||
constexpr auto GbaTileRows = 32;
|
||||
|
||||
int tileColumns(Context&) noexcept {
|
||||
return GbaTileColumns;
|
||||
}
|
||||
|
||||
int tileRows(Context&) noexcept {
|
||||
return GbaTileRows;
|
||||
}
|
||||
|
||||
ox::Error loadBgPalette(
|
||||
Context &ctx,
|
||||
size_t palBank,
|
||||
ox::StringViewCR palettePath) noexcept {
|
||||
OX_REQUIRE(pal, keel::readObj<CompactPalette>(keelCtx(ctx), palettePath));
|
||||
return loadBgPalette(ctx, palBank, *pal, 0);
|
||||
}
|
||||
|
||||
ox::Error loadBgPalette(
|
||||
Context &ctx,
|
||||
size_t palBank,
|
||||
ox::FileAddress const&paletteAddr) noexcept {
|
||||
OX_REQUIRE(pal, keel::readObj<CompactPalette>(keelCtx(ctx), paletteAddr));
|
||||
return loadBgPalette(ctx, palBank, *pal, 0);
|
||||
}
|
||||
|
||||
ox::Error loadSpritePalette(
|
||||
Context &ctx,
|
||||
ox::StringViewCR palettePath) noexcept {
|
||||
OX_REQUIRE(pal, keel::readObj<CompactPalette>(keelCtx(ctx), palettePath));
|
||||
return loadSpritePalette(ctx, *pal, 0);
|
||||
}
|
||||
|
||||
ox::Error loadSpritePalette(
|
||||
Context &ctx,
|
||||
ox::FileAddress const&paletteAddr) noexcept {
|
||||
OX_REQUIRE(pal, keel::readObj<CompactPalette>(keelCtx(ctx), paletteAddr));
|
||||
return loadSpritePalette(ctx, *pal, 0);
|
||||
}
|
||||
|
||||
ox::Error loadBgTileSheet(
|
||||
Context &ctx,
|
||||
unsigned cbb,
|
||||
ox::FileAddress const&tsAddr,
|
||||
size_t dstTileIdx,
|
||||
size_t srcTileIdx,
|
||||
size_t tileCnt) noexcept {
|
||||
OX_REQUIRE(ts, keel::readObj<CompactTileSheet>(keelCtx(ctx), tsAddr));
|
||||
return loadBgTileSheet(ctx, cbb, *ts, dstTileIdx, srcTileIdx, tileCnt);
|
||||
}
|
||||
|
||||
ox::Error loadBgTileSheet(
|
||||
Context &ctx,
|
||||
unsigned cbb,
|
||||
ox::StringViewCR tsPath,
|
||||
size_t dstTileIdx,
|
||||
size_t srcTileIdx,
|
||||
size_t tileCnt) noexcept {
|
||||
OX_REQUIRE(ts, keel::readObj<CompactTileSheet>(keelCtx(ctx), tsPath));
|
||||
return loadBgTileSheet(ctx, cbb, *ts, dstTileIdx, srcTileIdx, tileCnt);
|
||||
}
|
||||
|
||||
ox::Error loadBgTileSheet(
|
||||
Context &ctx,
|
||||
unsigned cbb,
|
||||
ox::StringViewCR tilesheetPath,
|
||||
ox::Optional<unsigned> const&paletteBank) noexcept {
|
||||
OX_REQUIRE(ts, keel::readObj<CompactTileSheet>(keelCtx(ctx), tilesheetPath));
|
||||
return loadBgTileSheet(ctx, cbb, *ts, paletteBank);
|
||||
}
|
||||
|
||||
ox::Error loadBgTileSheet(
|
||||
Context &ctx,
|
||||
unsigned cbb,
|
||||
ox::FileAddress const&tilesheetAddr,
|
||||
ox::Optional<unsigned> const&paletteBank) noexcept {
|
||||
OX_REQUIRE(ts, keel::readObj<CompactTileSheet>(keelCtx(ctx), tilesheetAddr));
|
||||
return loadBgTileSheet(ctx, cbb, *ts, paletteBank);
|
||||
}
|
||||
|
||||
ox::Error loadSpriteTileSheet(
|
||||
Context &ctx,
|
||||
ox::StringViewCR tilesheetPath,
|
||||
bool loadDefaultPalette) noexcept {
|
||||
OX_REQUIRE(ts, readObj<CompactTileSheet>(keelCtx(ctx), tilesheetPath));
|
||||
return loadSpriteTileSheet(ctx, *ts, loadDefaultPalette);
|
||||
}
|
||||
|
||||
ox::Error loadSpriteTileSheet(
|
||||
Context &ctx,
|
||||
ox::FileAddress const&tilesheetAddr,
|
||||
bool loadDefaultPalette) noexcept {
|
||||
OX_REQUIRE(ts, readObj<CompactTileSheet>(keelCtx(ctx), tilesheetAddr));
|
||||
return loadSpriteTileSheet(ctx, *ts, loadDefaultPalette);
|
||||
}
|
||||
|
||||
// map ASCII values to the nostalgia charset
|
||||
constexpr ox::Array<char, 128> charMap = {
|
||||
0,
|
||||
@@ -149,7 +247,7 @@ ox::Error initConsole(Context &ctx) noexcept {
|
||||
constexpr ox::FileAddress PaletteAddr = ox::StringLiteral("/Palettes/Charset.npal");
|
||||
setBgStatus(ctx, 0b0001);
|
||||
setBgCbb(ctx, 0, 0);
|
||||
oxReturnError(loadBgTileSheet(ctx, 0, TilesheetAddr));
|
||||
OX_RETURN_ERROR(loadBgTileSheet(ctx, 0, TilesheetAddr));
|
||||
return loadBgPalette(ctx, 0, PaletteAddr);
|
||||
}
|
||||
|
||||
@@ -157,13 +255,12 @@ void puts(
|
||||
Context &ctx,
|
||||
int const column,
|
||||
int const row,
|
||||
ox::CRStringView str) noexcept {
|
||||
auto const col = static_cast<uint_t>(column);
|
||||
ox::StringViewCR str) noexcept {
|
||||
for (auto i = 0u; i < str.bytes(); ++i) {
|
||||
setBgTile(
|
||||
ctx,
|
||||
0,
|
||||
static_cast<int>(col + i),
|
||||
column + static_cast<int>(i),
|
||||
row,
|
||||
static_cast<uint8_t>(charMap[static_cast<uint8_t>(str[i])]));
|
||||
}
|
||||
|
||||
@@ -18,6 +18,9 @@ static class: public keel::Module {
|
||||
private:
|
||||
NostalgiaPaletteToPaletteV1Converter m_nostalgiaPaletteToPaletteV1Converter;
|
||||
PaletteV1ToPaletteV2Converter m_paletteV1ToPaletteV2Converter;
|
||||
PaletteV2ToPaletteV3Converter m_paletteV2ToPaletteV3Converter;
|
||||
PaletteV3ToPaletteV4Converter m_paletteV3ToPaletteV4Converter;
|
||||
PaletteToCompactPaletteConverter m_paletteToCompactPaletteConverter;
|
||||
TileSheetV1ToTileSheetV2Converter m_tileSheetV1ToTileSheetV2Converter;
|
||||
TileSheetV2ToTileSheetV3Converter m_tileSheetV2ToTileSheetV3Converter;
|
||||
TileSheetV3ToTileSheetV4Converter m_tileSheetV3ToTileSheetV4Converter;
|
||||
@@ -36,9 +39,12 @@ static class: public keel::Module {
|
||||
keel::generateTypeDesc<TileSheetV2>,
|
||||
keel::generateTypeDesc<TileSheetV3>,
|
||||
keel::generateTypeDesc<TileSheetV4>,
|
||||
keel::generateTypeDesc<CompactTileSheet>,
|
||||
keel::generateTypeDesc<CompactTileSheetV1>,
|
||||
keel::generateTypeDesc<PaletteV1>,
|
||||
keel::generateTypeDesc<PaletteV2>,
|
||||
keel::generateTypeDesc<PaletteV3>,
|
||||
keel::generateTypeDesc<PaletteV4>,
|
||||
keel::generateTypeDesc<CompactPaletteV1>,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -47,6 +53,9 @@ static class: public keel::Module {
|
||||
return {
|
||||
&m_nostalgiaPaletteToPaletteV1Converter,
|
||||
&m_paletteV1ToPaletteV2Converter,
|
||||
&m_paletteV2ToPaletteV3Converter,
|
||||
&m_paletteV3ToPaletteV4Converter,
|
||||
&m_paletteToCompactPaletteConverter,
|
||||
&m_tileSheetV1ToTileSheetV2Converter,
|
||||
&m_tileSheetV2ToTileSheetV3Converter,
|
||||
&m_tileSheetV3ToTileSheetV4Converter,
|
||||
@@ -63,7 +72,7 @@ static class: public keel::Module {
|
||||
typeId == ox::ModelTypeId_v<TileSheetV2> ||
|
||||
typeId == ox::ModelTypeId_v<TileSheetV3> ||
|
||||
typeId == ox::ModelTypeId_v<TileSheetV4>) {
|
||||
oxReturnError(keel::convertBuffToBuff<CompactTileSheet>(
|
||||
OX_RETURN_ERROR(keel::convertBuffToBuff<CompactTileSheet>(
|
||||
ctx, buff, ox::ClawFormat::Metal).moveTo(buff));
|
||||
return true;
|
||||
}
|
||||
@@ -71,8 +80,11 @@ static class: public keel::Module {
|
||||
},
|
||||
[](keel::Context &ctx, ox::Buffer &buff, ox::StringView typeId) -> ox::Result<bool> {
|
||||
if (typeId == ox::ModelTypeId_v<NostalgiaPalette> ||
|
||||
typeId == ox::ModelTypeId_v<PaletteV1>) {
|
||||
oxReturnError(keel::convertBuffToBuff<Palette>(
|
||||
typeId == ox::ModelTypeId_v<PaletteV1> ||
|
||||
typeId == ox::ModelTypeId_v<PaletteV2> ||
|
||||
typeId == ox::ModelTypeId_v<PaletteV3> ||
|
||||
typeId == ox::ModelTypeId_v<PaletteV4>) {
|
||||
OX_RETURN_ERROR(keel::convertBuffToBuff<CompactPalette>(
|
||||
ctx, buff, ox::ClawFormat::Metal).moveTo(buff));
|
||||
return true;
|
||||
}
|
||||
@@ -80,7 +92,7 @@ static class: public keel::Module {
|
||||
},
|
||||
};
|
||||
}
|
||||
} mod;
|
||||
} const mod;
|
||||
|
||||
keel::Module const*keelModule() noexcept {
|
||||
return &mod;
|
||||
|
||||
@@ -22,6 +22,50 @@ ox::Error PaletteV1ToPaletteV2Converter::convert(
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Error PaletteV2ToPaletteV3Converter::convert(
|
||||
keel::Context&,
|
||||
PaletteV2 &src,
|
||||
PaletteV3 &dst) const noexcept {
|
||||
dst.pages = std::move(src.pages);
|
||||
if (!dst.pages.empty()) {
|
||||
dst.colorInfo.reserve(dst.pages[0].size());
|
||||
for (size_t i = 0; i < dst.pages[0].size(); ++i) {
|
||||
dst.colorInfo.emplace_back(ox::sfmt("Color {}", i + 1));
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Error PaletteV3ToPaletteV4Converter::convert(
|
||||
keel::Context&,
|
||||
PaletteV3 &src,
|
||||
PaletteV4 &dst) const noexcept {
|
||||
dst.pages.reserve(src.pages.size());
|
||||
for (auto i = 1; auto &page : src.pages) {
|
||||
dst.pages.emplace_back(ox::sfmt("Page {}", i), std::move(page));
|
||||
++i;
|
||||
}
|
||||
dst.colorNames.reserve(src.colorInfo.size());
|
||||
for (auto &ci : src.colorInfo) {
|
||||
dst.colorNames.emplace_back(std::move(ci.name));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Error PaletteToCompactPaletteConverter::convert(
|
||||
keel::Context&,
|
||||
Palette &src,
|
||||
CompactPalette &dst) const noexcept {
|
||||
dst.pages.reserve(src.pages.size());
|
||||
for (auto &page : src.pages) {
|
||||
auto &p = dst.pages.emplace_back();
|
||||
for (auto const c : page.colors) {
|
||||
p.emplace_back(c);
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Error TileSheetV1ToTileSheetV2Converter::convert(
|
||||
keel::Context&,
|
||||
TileSheetV1 &src,
|
||||
|
||||
@@ -21,7 +21,19 @@ class NostalgiaPaletteToPaletteV1Converter: public keel::Converter<NostalgiaPale
|
||||
};
|
||||
|
||||
class PaletteV1ToPaletteV2Converter: public keel::Converter<PaletteV1, PaletteV2> {
|
||||
ox::Error convert(keel::Context&, PaletteV1 &src, PaletteV2 &dst) const noexcept final;
|
||||
ox::Error convert(keel::Context&, PaletteV1 &src, PaletteV2 &dst) const noexcept final;
|
||||
};
|
||||
|
||||
class PaletteV2ToPaletteV3Converter: public keel::Converter<PaletteV2, PaletteV3> {
|
||||
ox::Error convert(keel::Context&, PaletteV2 &src, PaletteV3 &dst) const noexcept final;
|
||||
};
|
||||
|
||||
class PaletteV3ToPaletteV4Converter: public keel::Converter<PaletteV3, PaletteV4> {
|
||||
ox::Error convert(keel::Context&, PaletteV3 &src, PaletteV4 &dst) const noexcept final;
|
||||
};
|
||||
|
||||
class PaletteToCompactPaletteConverter: public keel::Converter<Palette, CompactPalette> {
|
||||
ox::Error convert(keel::Context&, Palette &src, CompactPalette &dst) const noexcept final;
|
||||
};
|
||||
|
||||
class TileSheetV1ToTileSheetV2Converter: public keel::Converter<TileSheetV1, TileSheetV2> {
|
||||
|
||||
@@ -25,7 +25,7 @@ Context::~Context() noexcept {
|
||||
|
||||
ox::Result<ContextUPtr> init(turbine::Context &tctx, InitParams const¶ms) noexcept {
|
||||
auto ctx = ox::make_unique<Context>(tctx, params);
|
||||
oxReturnError(initGfx(*ctx, params));
|
||||
OX_RETURN_ERROR(initGfx(*ctx, params));
|
||||
return ContextUPtr(ctx.release());
|
||||
}
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@ namespace nostalgia::core {
|
||||
|
||||
namespace renderer {
|
||||
|
||||
constexpr auto Scale = 1;
|
||||
constexpr auto PriorityScale = 0.01f;
|
||||
static constexpr auto Scale = 1;
|
||||
static constexpr auto PriorityScale = 0.01f;
|
||||
|
||||
Drawer::Drawer(Context &ctx) noexcept: m_ctx(ctx) {}
|
||||
|
||||
@@ -31,7 +31,7 @@ void Drawer::draw(turbine::Context &tctx) noexcept {
|
||||
core::gl::draw(m_ctx, turbine::getScreenSize(tctx));
|
||||
}
|
||||
|
||||
constexpr ox::CStringView bgvshadTmpl = R"glsl(
|
||||
static constexpr ox::CStringView bgvshadTmpl = R"glsl(
|
||||
{}
|
||||
in vec2 vTexCoord;
|
||||
in vec3 vPosition;
|
||||
@@ -55,7 +55,7 @@ constexpr ox::CStringView bgvshadTmpl = R"glsl(
|
||||
fPalOffset = vPalOffset;
|
||||
})glsl";
|
||||
|
||||
constexpr ox::CStringView bgfshadTmpl = R"glsl(
|
||||
static constexpr ox::CStringView bgfshadTmpl = R"glsl(
|
||||
{}
|
||||
out vec4 outColor;
|
||||
in float fPalOffset;
|
||||
@@ -71,7 +71,7 @@ constexpr ox::CStringView bgfshadTmpl = R"glsl(
|
||||
}
|
||||
})glsl";
|
||||
|
||||
constexpr ox::CStringView spritevshadTmpl = R"glsl(
|
||||
static constexpr ox::CStringView spritevshadTmpl = R"glsl(
|
||||
{}
|
||||
in float vEnabled;
|
||||
in vec3 vPosition;
|
||||
@@ -90,7 +90,7 @@ constexpr ox::CStringView spritevshadTmpl = R"glsl(
|
||||
fTexCoord = vTexCoord * vec2(1, vTileHeight);
|
||||
})glsl";
|
||||
|
||||
constexpr ox::CStringView spritefshadTmpl = R"glsl(
|
||||
static constexpr ox::CStringView spritefshadTmpl = R"glsl(
|
||||
{}
|
||||
out vec4 outColor;
|
||||
in vec2 fTexCoord;
|
||||
@@ -279,7 +279,7 @@ static void initBackgroundBufferset(
|
||||
static glutils::GLTexture createTexture(
|
||||
GLsizei w,
|
||||
GLsizei h,
|
||||
const void *pixels) noexcept {
|
||||
void const*pixels) noexcept {
|
||||
GLuint texId = 0;
|
||||
glGenTextures(1, &texId);
|
||||
glutils::GLTexture tex(texId);
|
||||
@@ -364,7 +364,7 @@ static void loadPalette(
|
||||
ox::Array<GLfloat, 1024> &palette,
|
||||
size_t palOffset,
|
||||
GLuint shaderPgrm,
|
||||
Palette const&pal,
|
||||
CompactPalette const&pal,
|
||||
size_t page = 0) noexcept {
|
||||
static constexpr std::size_t ColorCnt = 256;
|
||||
for (auto i = palOffset; auto const c : pal.pages[page]) {
|
||||
@@ -385,7 +385,8 @@ static void setSprite(
|
||||
uint_t const idx,
|
||||
Sprite const&s) noexcept {
|
||||
// Tonc Table 8.4
|
||||
static constexpr ox::Array<ox::Vec<uint_t>, 12> dimensions{
|
||||
struct Sz { uint_t x{}, y{}; };
|
||||
static constexpr ox::Array<Sz, 12> dimensions{
|
||||
// col 0
|
||||
{1, 1}, // 0, 0
|
||||
{2, 2}, // 0, 1
|
||||
@@ -463,8 +464,8 @@ ox::Error initGfx(
|
||||
const auto bgFshad = ox::sfmt(renderer::bgfshadTmpl, gl::GlslVersion);
|
||||
const auto spriteVshad = ox::sfmt(renderer::spritevshadTmpl, gl::GlslVersion);
|
||||
const auto spriteFshad = ox::sfmt(renderer::spritefshadTmpl, gl::GlslVersion);
|
||||
oxReturnError(glutils::buildShaderProgram(bgVshad, bgFshad).moveTo(ctx.bgShader));
|
||||
oxReturnError(
|
||||
OX_RETURN_ERROR(glutils::buildShaderProgram(bgVshad, bgFshad).moveTo(ctx.bgShader));
|
||||
OX_RETURN_ERROR(
|
||||
glutils::buildShaderProgram(spriteVshad, spriteFshad).moveTo(ctx.spriteShader));
|
||||
for (auto &cbb : ctx.cbbs) {
|
||||
initBackgroundBufferset(ctx.bgShader, cbb);
|
||||
@@ -491,22 +492,22 @@ struct TileSheetData {
|
||||
};
|
||||
|
||||
static ox::Result<TileSheetData> normalizeTileSheet(
|
||||
CompactTileSheet const&tilesheet) noexcept {
|
||||
const uint_t bytesPerTile = tilesheet.bpp == 8 ? PixelsPerTile : PixelsPerTile / 2;
|
||||
const auto tiles = tilesheet.pixels.size() / bytesPerTile;
|
||||
CompactTileSheet const&ts) noexcept {
|
||||
const uint_t bytesPerTile = ts.bpp == 8 ? PixelsPerTile : PixelsPerTile / 2;
|
||||
const auto tiles = ts.pixels.size() / bytesPerTile;
|
||||
constexpr int width = 8;
|
||||
const int height = 8 * static_cast<int>(tiles);
|
||||
ox::Vector<uint32_t> pixels;
|
||||
if (bytesPerTile == 64) { // 8 BPP
|
||||
pixels.resize(tilesheet.pixels.size());
|
||||
for (std::size_t i = 0; i < tilesheet.pixels.size(); ++i) {
|
||||
pixels[i] = tilesheet.pixels[i];
|
||||
pixels.resize(ts.pixels.size());
|
||||
for (std::size_t i = 0; i < ts.pixels.size(); ++i) {
|
||||
pixels[i] = ts.pixels[i];
|
||||
}
|
||||
} else { // 4 BPP
|
||||
pixels.resize(tilesheet.pixels.size() * 2);
|
||||
for (std::size_t i = 0; i < tilesheet.pixels.size(); ++i) {
|
||||
pixels[i * 2 + 0] = tilesheet.pixels[i] & 0xF;
|
||||
pixels[i * 2 + 1] = tilesheet.pixels[i] >> 4;
|
||||
pixels.resize(ts.pixels.size() * 2);
|
||||
for (std::size_t i = 0; i < ts.pixels.size(); ++i) {
|
||||
pixels[i * 2 + 0] = ts.pixels[i] & 0xF;
|
||||
pixels[i * 2 + 1] = ts.pixels[i] >> 4;
|
||||
}
|
||||
}
|
||||
return TileSheetData{std::move(pixels), width, height};
|
||||
@@ -515,7 +516,7 @@ static ox::Result<TileSheetData> normalizeTileSheet(
|
||||
ox::Error loadBgPalette(
|
||||
Context &ctx,
|
||||
size_t palBank,
|
||||
Palette const&palette,
|
||||
CompactPalette const&palette,
|
||||
size_t page) noexcept {
|
||||
renderer::loadPalette(ctx.bgPalette, palBank * 16 * 4, ctx.bgShader, palette, page);
|
||||
return {};
|
||||
@@ -523,33 +524,13 @@ ox::Error loadBgPalette(
|
||||
|
||||
ox::Error loadSpritePalette(
|
||||
Context &ctx,
|
||||
Palette const&palette,
|
||||
CompactPalette const&palette,
|
||||
size_t page) noexcept {
|
||||
ox::Array<GLfloat, 1024> pal;
|
||||
renderer::loadPalette(pal, 0, ctx.spriteShader, palette, page);
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Error loadBgPalette(
|
||||
Context &ctx,
|
||||
size_t palBank,
|
||||
ox::FileAddress const&paletteAddr) noexcept {
|
||||
auto &kctx = keelCtx(ctx.turbineCtx);
|
||||
oxRequire(palette, readObj<Palette>(kctx, paletteAddr));
|
||||
renderer::loadPalette(ctx.bgPalette, palBank * 16 * 4, ctx.bgShader, *palette);
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Error loadSpritePalette(
|
||||
Context &ctx,
|
||||
ox::FileAddress const&paletteAddr) noexcept {
|
||||
auto &kctx = keelCtx(ctx.turbineCtx);
|
||||
oxRequire(palette, readObj<Palette>(kctx, paletteAddr));
|
||||
ox::Array<GLfloat, 1024> pal;
|
||||
renderer::loadPalette(pal, 0, ctx.spriteShader, *palette);
|
||||
return {};
|
||||
}
|
||||
|
||||
static ox::Result<TileSheetData> buildSetTsd(
|
||||
Context &ctx,
|
||||
TileSheetSet const&set) noexcept {
|
||||
@@ -557,8 +538,8 @@ static ox::Result<TileSheetData> buildSetTsd(
|
||||
TileSheetData setTsd;
|
||||
setTsd.width = TileWidth;
|
||||
for (auto const&entry : set.entries) {
|
||||
oxRequire(tilesheet, readObj<CompactTileSheet>(kctx, entry.tilesheet));
|
||||
oxRequire(tsd, normalizeTileSheet(*tilesheet));
|
||||
OX_REQUIRE(tilesheet, readObj<CompactTileSheet>(kctx, entry.tilesheet));
|
||||
OX_REQUIRE(tsd, normalizeTileSheet(*tilesheet));
|
||||
for (auto const&s : entry.sections) {
|
||||
auto const size = s.tiles * PixelsPerTile;
|
||||
for (auto i = 0; i < size; ++i) {
|
||||
@@ -571,18 +552,61 @@ static ox::Result<TileSheetData> buildSetTsd(
|
||||
return setTsd;
|
||||
}
|
||||
|
||||
static void copyPixels(
|
||||
CompactTileSheet const&ts,
|
||||
ox::Span<uint32_t> dst,
|
||||
size_t const srcPxIdx,
|
||||
size_t pxlCnt) noexcept {
|
||||
size_t idx{};
|
||||
if (ts.bpp == 4) {
|
||||
for (size_t i = 0; i < pxlCnt; i += 2) {
|
||||
auto const [a, b] = get2Pixels4Bpp(ts, i + srcPxIdx);
|
||||
dst[idx++] = a;
|
||||
dst[idx++] = b;
|
||||
}
|
||||
} else if (ts.bpp == 8) {
|
||||
for (size_t i = 0; i < pxlCnt; i += 2) {
|
||||
auto const [a, b] = get2Pixels8Bpp(ts, i + srcPxIdx);
|
||||
dst[idx++] = a;
|
||||
dst[idx++] = b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ox::Error loadBgTileSheet(
|
||||
Context &ctx,
|
||||
unsigned const cbb,
|
||||
CompactTileSheet const&ts,
|
||||
size_t const dstTileIdx,
|
||||
size_t const srcTileIdx,
|
||||
size_t const tileCnt) noexcept {
|
||||
auto &cbbPxls = ctx.cbbs[cbb].pixels;
|
||||
auto const bytesPerTile = static_cast<uint64_t>(PixelsPerTile / (1 + (ts.bpp == 4)));
|
||||
auto const pxlCnt = tileCnt * PixelsPerTile;
|
||||
auto const srcPxIdx = srcTileIdx * PixelsPerTile;
|
||||
auto const dstPxIdx = dstTileIdx * PixelsPerTile;
|
||||
if (dstPxIdx + pxlCnt >= cbbPxls.size()) {
|
||||
return ox::Error(1, "video mem dst overflow");
|
||||
}
|
||||
auto const dst = ox::Span{cbbPxls} + dstPxIdx;
|
||||
copyPixels(ts, dst, srcPxIdx, pxlCnt);
|
||||
auto const cbbTiles = cbbPxls.size() / bytesPerTile;
|
||||
int constexpr cbbWidth = 8;
|
||||
int const cbbHeight = 8 * static_cast<int>(cbbTiles);
|
||||
ctx.cbbs[cbb].tex = renderer::createTexture(cbbWidth, cbbHeight, cbbPxls.data());
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Error loadBgTileSheet(
|
||||
Context &ctx,
|
||||
uint_t cbb,
|
||||
ox::FileAddress const&tilesheetAddr,
|
||||
CompactTileSheet const&ts,
|
||||
ox::Optional<unsigned> const&paletteBank) noexcept {
|
||||
auto &kctx = keelCtx(ctx.turbineCtx);
|
||||
oxRequire(tilesheet, readObj<CompactTileSheet>(kctx, tilesheetAddr));
|
||||
oxRequire(tsd, normalizeTileSheet(*tilesheet));
|
||||
oxTracef("nostalgia.core.gfx.gl", "loadBgTexture: { cbbIdx: {}, w: {}, h: {} }", cbb, tsd.width, tsd.height);
|
||||
ctx.cbbs[cbb].tex = renderer::createTexture(tsd.width, tsd.height, tsd.pixels.data());
|
||||
if (paletteBank.has_value() && tilesheet->defaultPalette) {
|
||||
oxReturnError(loadBgPalette(ctx, *paletteBank, tilesheet->defaultPalette));
|
||||
auto const bytesPerTile = static_cast<uint64_t>(PixelsPerTile / (1 + (ts.bpp == 4)));
|
||||
auto const tiles = ts.pixels.size() / bytesPerTile;
|
||||
OX_RETURN_ERROR(loadBgTileSheet(ctx, cbb, ts, 0, 0, tiles));
|
||||
if (paletteBank.has_value() && ts.defaultPalette) {
|
||||
OX_RETURN_ERROR(loadBgPalette(ctx, *paletteBank, ts.defaultPalette));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
@@ -591,22 +615,20 @@ ox::Error loadBgTileSheet(
|
||||
Context &ctx,
|
||||
unsigned cbb,
|
||||
TileSheetSet const&set) noexcept {
|
||||
oxRequire(setTsd, buildSetTsd(ctx, set));
|
||||
OX_REQUIRE(setTsd, buildSetTsd(ctx, set));
|
||||
ctx.cbbs[cbb].tex = renderer::createTexture(setTsd.width, setTsd.height, setTsd.pixels.data());
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Error loadSpriteTileSheet(
|
||||
Context &ctx,
|
||||
ox::FileAddress const&tilesheetAddr,
|
||||
CompactTileSheet const&ts,
|
||||
bool loadDefaultPalette) noexcept {
|
||||
auto &kctx = keelCtx(ctx.turbineCtx);
|
||||
oxRequire(tilesheet, readObj<CompactTileSheet>(kctx, tilesheetAddr));
|
||||
oxRequire(tsd, normalizeTileSheet(*tilesheet));
|
||||
OX_REQUIRE(tsd, normalizeTileSheet(ts));
|
||||
oxTracef("nostalgia.core.gfx.gl", "loadSpriteTexture: { w: {}, h: {} }", tsd.width, tsd.height);
|
||||
ctx.spriteBlocks.tex = renderer::createTexture(tsd.width, tsd.height, tsd.pixels.data());
|
||||
if (loadDefaultPalette) {
|
||||
oxReturnError(loadSpritePalette(ctx, tilesheet->defaultPalette));
|
||||
OX_RETURN_ERROR(loadSpritePalette(ctx, ts.defaultPalette));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
@@ -614,7 +636,7 @@ ox::Error loadSpriteTileSheet(
|
||||
ox::Error loadSpriteTileSheet(
|
||||
Context &ctx,
|
||||
TileSheetSet const&set) noexcept {
|
||||
oxRequire(setTsd, buildSetTsd(ctx, set));
|
||||
OX_REQUIRE(setTsd, buildSetTsd(ctx, set));
|
||||
ctx.spriteBlocks.tex = renderer::createTexture(setTsd.width, setTsd.height, setTsd.pixels.data());
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ constexpr uint64_t SpriteVertexEboLength = 6;
|
||||
|
||||
struct CBB: public glutils::BufferSet {
|
||||
bool updated = false;
|
||||
ox::Array<uint32_t, 32768> pixels;
|
||||
constexpr CBB() noexcept {
|
||||
vertices.resize(TileCount * BgVertexVboLength);
|
||||
elements.resize(TileCount * BgVertexEboLength);
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
target_sources(
|
||||
NostalgiaCore-Studio PRIVATE
|
||||
paletteeditor.cpp
|
||||
commands/addcolorcommand.cpp
|
||||
commands/applycolorallpagescommand.cpp
|
||||
commands/duplicatepagecommand.cpp
|
||||
commands/movecolorcommand.cpp
|
||||
commands/removecolorcommand.cpp
|
||||
commands/removepagecommand.cpp
|
||||
commands/renamepagecommand.cpp
|
||||
commands/updatecolorcommand.cpp
|
||||
commands/updatecolorinfocommand.cpp
|
||||
paletteeditor-imgui.cpp
|
||||
)
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2016 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include "commands.hpp"
|
||||
#include "addcolorcommand.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
AddColorCommand::AddColorCommand(Palette &pal, Color16 const color, size_t const idx) noexcept:
|
||||
m_pal(pal),
|
||||
m_color(color),
|
||||
m_idx(idx) {}
|
||||
|
||||
int AddColorCommand::commandId() const noexcept {
|
||||
return static_cast<int>(PaletteEditorCommandId::AddColor);
|
||||
}
|
||||
|
||||
ox::Error AddColorCommand::redo() noexcept {
|
||||
m_pal.colorNames.emplace(m_idx, ox::sfmt("Color {}", m_pal.colorNames.size() + 1));
|
||||
for (auto &page : m_pal.pages) {
|
||||
page.colors.emplace(m_idx, m_color);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Error AddColorCommand::undo() noexcept {
|
||||
OX_RETURN_ERROR(m_pal.colorNames.erase(m_idx));
|
||||
for (auto &page : m_pal.pages) {
|
||||
OX_RETURN_ERROR(page.colors.erase(m_idx));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2016 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <studio/studio.hpp>
|
||||
|
||||
#include <nostalgia/core/palette.hpp>
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
class AddColorCommand: public studio::UndoCommand {
|
||||
private:
|
||||
Palette &m_pal;
|
||||
Color16 m_color = 0;
|
||||
size_t const m_idx = 0;
|
||||
|
||||
public:
|
||||
AddColorCommand(Palette &pal, Color16 color, size_t idx) noexcept;
|
||||
|
||||
~AddColorCommand() noexcept override = default;
|
||||
|
||||
[[nodiscard]]
|
||||
int commandId() const noexcept override;
|
||||
|
||||
ox::Error redo() noexcept override;
|
||||
|
||||
ox::Error undo() noexcept override;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2016 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include "commands.hpp"
|
||||
#include "applycolorallpagescommand.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
ApplyColorAllPagesCommand::ApplyColorAllPagesCommand(Palette &pal, size_t const page, size_t const idx):
|
||||
m_pal(pal),
|
||||
m_page(page),
|
||||
m_idx(idx),
|
||||
m_origColors([this] {
|
||||
ox::Vector<Color16> colors;
|
||||
colors.reserve(m_pal.pages.size());
|
||||
for (auto const&p : m_pal.pages) {
|
||||
colors.emplace_back(p.colors[m_idx]);
|
||||
}
|
||||
return colors;
|
||||
}()) {
|
||||
auto const c = color(m_pal, m_page, m_idx);
|
||||
if (ox::all_of(m_pal.pages.begin(), m_pal.pages.end(), [this, c](PalettePage const&page) {
|
||||
return page.colors[m_idx] == c;
|
||||
})) {
|
||||
throw studio::NoChangesException();
|
||||
}
|
||||
}
|
||||
|
||||
int ApplyColorAllPagesCommand::commandId() const noexcept {
|
||||
return static_cast<int>(PaletteEditorCommandId::ApplyColorAllPages);
|
||||
}
|
||||
|
||||
ox::Error ApplyColorAllPagesCommand::redo() noexcept {
|
||||
auto const c = color(m_pal, m_page, m_idx);
|
||||
for (auto &page : m_pal.pages) {
|
||||
page.colors[m_idx] = c;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Error ApplyColorAllPagesCommand::undo() noexcept {
|
||||
for (size_t p = 0u; auto &page : m_pal.pages) {
|
||||
page.colors[m_idx] = m_origColors[p];
|
||||
++p;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2016 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <studio/studio.hpp>
|
||||
|
||||
#include <nostalgia/core/palette.hpp>
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
class ApplyColorAllPagesCommand: public studio::UndoCommand {
|
||||
private:
|
||||
Palette &m_pal;
|
||||
size_t const m_page{};
|
||||
size_t const m_idx{};
|
||||
ox::Vector<Color16> const m_origColors;
|
||||
|
||||
public:
|
||||
ApplyColorAllPagesCommand(Palette &pal, size_t page, size_t idx);
|
||||
|
||||
~ApplyColorAllPagesCommand() noexcept override = default;
|
||||
|
||||
[[nodiscard]]
|
||||
int commandId() const noexcept final;
|
||||
|
||||
ox::Error redo() noexcept final;
|
||||
|
||||
ox::Error undo() noexcept final;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright 2016 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
enum class PaletteEditorCommandId {
|
||||
ApplyColorAllPages,
|
||||
RenamePage,
|
||||
DuplicatePage,
|
||||
RemovePage,
|
||||
AddColor,
|
||||
RemoveColor,
|
||||
UpdateColorInfo,
|
||||
UpdateColor,
|
||||
MoveColor,
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2016 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include "commands.hpp"
|
||||
|
||||
#include "duplicatepagecommand.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
DuplicatePageCommand::DuplicatePageCommand(Palette &pal, size_t srcIdx, size_t dstIdx) noexcept:
|
||||
m_pal(pal),
|
||||
m_dstIdx(dstIdx) {
|
||||
auto const&src = m_pal.pages[srcIdx];
|
||||
m_page.reserve(src.colors.size());
|
||||
for (auto const&s : src.colors) {
|
||||
m_page.emplace_back(s);
|
||||
}
|
||||
}
|
||||
|
||||
int DuplicatePageCommand::commandId() const noexcept {
|
||||
return static_cast<int>(PaletteEditorCommandId::DuplicatePage);
|
||||
}
|
||||
|
||||
ox::Error DuplicatePageCommand::redo() noexcept {
|
||||
m_pal.pages.emplace(m_dstIdx, "", std::move(m_page));
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Error DuplicatePageCommand::undo() noexcept {
|
||||
m_page = std::move(m_pal.pages[m_dstIdx].colors);
|
||||
return m_pal.pages.erase(m_dstIdx).error;
|
||||
}
|
||||
|
||||
size_t DuplicatePageCommand::insertIdx() const noexcept {
|
||||
return m_dstIdx;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2016 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <studio/studio.hpp>
|
||||
|
||||
#include <nostalgia/core/palette.hpp>
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
class DuplicatePageCommand: public studio::UndoCommand {
|
||||
private:
|
||||
Palette &m_pal;
|
||||
size_t m_dstIdx = 0;
|
||||
ox::Vector<PaletteColor> m_page;
|
||||
|
||||
public:
|
||||
DuplicatePageCommand(Palette &pal, size_t srcIdx, size_t dstIdx) noexcept;
|
||||
|
||||
~DuplicatePageCommand() noexcept override = default;
|
||||
|
||||
[[nodiscard]]
|
||||
int commandId() const noexcept final;
|
||||
|
||||
ox::Error redo() noexcept final;
|
||||
|
||||
ox::Error undo() noexcept final;
|
||||
|
||||
[[nodiscard]]
|
||||
size_t insertIdx() const noexcept;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2016 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include "commands.hpp"
|
||||
|
||||
#include "movecolorcommand.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
MoveColorCommand::MoveColorCommand(
|
||||
Palette &pal, size_t page, size_t srcIdx, size_t dstIdx) noexcept:
|
||||
m_pal(pal),
|
||||
m_page(page),
|
||||
m_srcIdx(srcIdx),
|
||||
m_dstIdx(dstIdx) {}
|
||||
|
||||
int MoveColorCommand::commandId() const noexcept {
|
||||
return static_cast<int>(PaletteEditorCommandId::MoveColor);
|
||||
}
|
||||
|
||||
ox::Error MoveColorCommand::redo() noexcept {
|
||||
moveColor(m_srcIdx, m_dstIdx);
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Error MoveColorCommand::undo() noexcept {
|
||||
moveColor(m_dstIdx, m_srcIdx);
|
||||
return {};
|
||||
}
|
||||
|
||||
void MoveColorCommand::moveColor(size_t srcIdx, size_t dstIdx) noexcept {
|
||||
auto const c = color(m_pal, m_page, srcIdx);
|
||||
std::ignore = colors(m_pal, m_page).erase(srcIdx);
|
||||
colors(m_pal, m_page).emplace(dstIdx, c);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2016 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <studio/studio.hpp>
|
||||
|
||||
#include <nostalgia/core/palette.hpp>
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
class MoveColorCommand: public studio::UndoCommand {
|
||||
private:
|
||||
Palette &m_pal;
|
||||
size_t const m_page = 0;
|
||||
std::size_t const m_srcIdx = 0;
|
||||
std::size_t const m_dstIdx = 0;
|
||||
|
||||
public:
|
||||
MoveColorCommand(Palette &pal, size_t page, size_t srcIdx, size_t dstIdx) noexcept;
|
||||
|
||||
~MoveColorCommand() noexcept override = default;
|
||||
|
||||
[[nodiscard]]
|
||||
int commandId() const noexcept override;
|
||||
|
||||
ox::Error redo() noexcept override;
|
||||
|
||||
ox::Error undo() noexcept override;
|
||||
|
||||
private:
|
||||
void moveColor(size_t srcIdx, size_t dstIdx) noexcept;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2016 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include "commands.hpp"
|
||||
#include "removecolorcommand.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
RemoveColorCommand::RemoveColorCommand(Palette &pal, size_t const idx) noexcept:
|
||||
m_pal(pal),
|
||||
m_idx(idx),
|
||||
m_colors([this] {
|
||||
ox::Vector<Color16> colors;
|
||||
colors.reserve(m_pal.pages.size());
|
||||
for (auto const&p : m_pal.pages) {
|
||||
colors.emplace_back(p.colors[m_idx]);
|
||||
}
|
||||
return colors;
|
||||
}()) {}
|
||||
|
||||
int RemoveColorCommand::commandId() const noexcept {
|
||||
return static_cast<int>(PaletteEditorCommandId::RemoveColor);
|
||||
}
|
||||
|
||||
ox::Error RemoveColorCommand::redo() noexcept {
|
||||
m_colorInfo = std::move(m_pal.colorNames[m_idx]);
|
||||
OX_RETURN_ERROR(m_pal.colorNames.erase(m_idx));
|
||||
for (auto &page : m_pal.pages) {
|
||||
OX_RETURN_ERROR(page.colors.erase(m_idx));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Error RemoveColorCommand::undo() noexcept {
|
||||
m_pal.colorNames.emplace(m_idx, std::move(m_colorInfo));
|
||||
for (size_t p = 0; auto &page : m_pal.pages) {
|
||||
page.colors.emplace(m_idx, m_colors[p]);
|
||||
++p;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2016 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <studio/studio.hpp>
|
||||
|
||||
#include <nostalgia/core/palette.hpp>
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
class RemoveColorCommand: public studio::UndoCommand {
|
||||
private:
|
||||
Palette &m_pal;
|
||||
size_t const m_idx = 0;
|
||||
ox::String m_colorInfo;
|
||||
ox::Vector<Color16> const m_colors;
|
||||
|
||||
public:
|
||||
RemoveColorCommand(Palette &pal, size_t idx) noexcept;
|
||||
|
||||
~RemoveColorCommand() noexcept override = default;
|
||||
|
||||
[[nodiscard]]
|
||||
int commandId() const noexcept override;
|
||||
|
||||
ox::Error redo() noexcept override;
|
||||
|
||||
ox::Error undo() noexcept override;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2016 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include "commands.hpp"
|
||||
|
||||
#include "removepagecommand.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
RemovePageCommand::RemovePageCommand(Palette &pal, size_t idx) noexcept:
|
||||
m_pal(pal),
|
||||
m_idx(idx) {}
|
||||
|
||||
int RemovePageCommand::commandId() const noexcept {
|
||||
return static_cast<int>(PaletteEditorCommandId::RemovePage);
|
||||
}
|
||||
|
||||
ox::Error RemovePageCommand::redo() noexcept {
|
||||
m_page = std::move(m_pal.pages[m_idx]);
|
||||
return m_pal.pages.erase(m_idx).error;
|
||||
}
|
||||
|
||||
ox::Error RemovePageCommand::undo() noexcept {
|
||||
m_pal.pages.emplace(m_idx, std::move(m_page));
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2016 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <studio/studio.hpp>
|
||||
|
||||
#include <nostalgia/core/palette.hpp>
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
class RemovePageCommand: public studio::UndoCommand {
|
||||
private:
|
||||
Palette &m_pal;
|
||||
size_t m_idx = 0;
|
||||
PalettePage m_page;
|
||||
|
||||
public:
|
||||
RemovePageCommand(Palette &pal, size_t idx) noexcept;
|
||||
|
||||
~RemovePageCommand() noexcept override = default;
|
||||
|
||||
[[nodiscard]]
|
||||
int commandId() const noexcept final;
|
||||
|
||||
ox::Error redo() noexcept final;
|
||||
|
||||
ox::Error undo() noexcept final;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2016 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include "renamepagecommand.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
RenamePageCommand::RenamePageCommand(Palette &pal, size_t const page, ox::StringParam name) noexcept:
|
||||
m_pal(pal),
|
||||
m_page(page),
|
||||
m_name{std::move(name)} {}
|
||||
|
||||
int RenamePageCommand::commandId() const noexcept {
|
||||
return static_cast<int>(PaletteEditorCommandId::RenamePage);
|
||||
}
|
||||
|
||||
ox::Error RenamePageCommand::redo() noexcept {
|
||||
std::swap(m_pal.pages[m_page].name, m_name);
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Error RenamePageCommand::undo() noexcept {
|
||||
std::swap(m_pal.pages[m_page].name, m_name);
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2016 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <studio/studio.hpp>
|
||||
|
||||
#include <nostalgia/core/palette.hpp>
|
||||
|
||||
#include "commands.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
class RenamePageCommand: public studio::UndoCommand {
|
||||
private:
|
||||
Palette &m_pal;
|
||||
size_t m_page = 0;
|
||||
ox::String m_name;
|
||||
|
||||
public:
|
||||
RenamePageCommand(Palette &pal, size_t page, ox::StringParam name) noexcept;
|
||||
|
||||
~RenamePageCommand() noexcept override = default;
|
||||
|
||||
[[nodiscard]]
|
||||
int commandId() const noexcept final;
|
||||
|
||||
ox::Error redo() noexcept final;
|
||||
|
||||
ox::Error undo() noexcept final;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2016 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include "commands.hpp"
|
||||
#include "updatecolorcommand.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
UpdateColorCommand::UpdateColorCommand(
|
||||
Palette &pal,
|
||||
size_t page,
|
||||
size_t idx,
|
||||
Color16 newColor):
|
||||
m_pal(pal),
|
||||
m_page(page),
|
||||
m_idx(idx),
|
||||
m_altColor(newColor) {
|
||||
if (color(m_pal, m_page, m_idx) == newColor) {
|
||||
throw studio::NoChangesException();
|
||||
}
|
||||
}
|
||||
|
||||
bool UpdateColorCommand::mergeWith(UndoCommand &cmd) noexcept {
|
||||
if (cmd.commandId() != static_cast<int>(PaletteEditorCommandId::UpdateColor)) {
|
||||
return false;
|
||||
}
|
||||
auto ucCmd = dynamic_cast<UpdateColorCommand const*>(&cmd);
|
||||
if (m_idx != ucCmd->m_idx) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int UpdateColorCommand::commandId() const noexcept {
|
||||
return static_cast<int>(PaletteEditorCommandId::UpdateColor);
|
||||
}
|
||||
|
||||
ox::Error UpdateColorCommand::redo() noexcept {
|
||||
swap();
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Error UpdateColorCommand::undo() noexcept {
|
||||
swap();
|
||||
return {};
|
||||
}
|
||||
|
||||
void UpdateColorCommand::swap() noexcept {
|
||||
auto &dst = colors(m_pal, m_page)[m_idx];
|
||||
std::swap(dst, m_altColor);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2016 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <studio/studio.hpp>
|
||||
|
||||
#include <nostalgia/core/palette.hpp>
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
class UpdateColorCommand: public studio::UndoCommand {
|
||||
private:
|
||||
Palette &m_pal;
|
||||
size_t const m_page = 0;
|
||||
size_t const m_idx{};
|
||||
PaletteColor m_altColor{};
|
||||
|
||||
public:
|
||||
UpdateColorCommand(
|
||||
Palette &pal,
|
||||
size_t page,
|
||||
size_t idx,
|
||||
Color16 newColor);
|
||||
|
||||
~UpdateColorCommand() noexcept override = default;
|
||||
|
||||
[[nodiscard]]
|
||||
bool mergeWith(UndoCommand &cmd) noexcept final;
|
||||
|
||||
[[nodiscard]]
|
||||
int commandId() const noexcept final;
|
||||
|
||||
ox::Error redo() noexcept final;
|
||||
|
||||
ox::Error undo() noexcept final;
|
||||
|
||||
private:
|
||||
void swap() noexcept;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2016 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include "commands.hpp"
|
||||
#include "updatecolorinfocommand.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
UpdateColorInfoCommand::UpdateColorInfoCommand(
|
||||
Palette &pal,
|
||||
size_t idx,
|
||||
ox::StringParam newColorInfo):
|
||||
m_pal(pal),
|
||||
m_idx(idx),
|
||||
m_altColorInfo(std::move(newColorInfo)) {
|
||||
if (m_pal.colorNames[m_idx] == m_altColorInfo) {
|
||||
throw studio::NoChangesException();
|
||||
}
|
||||
}
|
||||
|
||||
bool UpdateColorInfoCommand::mergeWith(UndoCommand &cmd) noexcept {
|
||||
if (cmd.commandId() != static_cast<int>(PaletteEditorCommandId::UpdateColorInfo)) {
|
||||
return false;
|
||||
}
|
||||
auto ucCmd = dynamic_cast<UpdateColorInfoCommand const*>(&cmd);
|
||||
if (m_idx != ucCmd->m_idx) {
|
||||
return false;
|
||||
}
|
||||
m_pal.colorNames[m_idx] = std::move(ucCmd->m_pal.colorNames[m_idx]);
|
||||
setObsolete(m_altColorInfo == m_pal.colorNames[m_idx]);
|
||||
return true;
|
||||
}
|
||||
|
||||
int UpdateColorInfoCommand::commandId() const noexcept {
|
||||
return static_cast<int>(PaletteEditorCommandId::UpdateColorInfo);
|
||||
}
|
||||
|
||||
ox::Error UpdateColorInfoCommand::redo() noexcept {
|
||||
swap();
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Error UpdateColorInfoCommand::undo() noexcept {
|
||||
swap();
|
||||
return {};
|
||||
}
|
||||
|
||||
void UpdateColorInfoCommand::swap() noexcept {
|
||||
std::swap(m_pal.colorNames[m_idx], m_altColorInfo);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2016 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <studio/studio.hpp>
|
||||
|
||||
#include <nostalgia/core/palette.hpp>
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
class UpdateColorInfoCommand: public studio::UndoCommand {
|
||||
private:
|
||||
Palette &m_pal;
|
||||
size_t const m_idx{};
|
||||
ox::String m_altColorInfo;
|
||||
|
||||
public:
|
||||
UpdateColorInfoCommand(
|
||||
Palette &pal,
|
||||
size_t idx,
|
||||
ox::StringParam newColorInfo);
|
||||
|
||||
~UpdateColorInfoCommand() noexcept override = default;
|
||||
|
||||
[[nodiscard]]
|
||||
bool mergeWith(UndoCommand &cmd) noexcept final;
|
||||
|
||||
[[nodiscard]]
|
||||
int commandId() const noexcept final;
|
||||
|
||||
ox::Error redo() noexcept final;
|
||||
|
||||
ox::Error undo() noexcept final;
|
||||
|
||||
private:
|
||||
void swap() noexcept;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
@@ -4,61 +4,77 @@
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include <ox/std/memory.hpp>
|
||||
|
||||
#include <keel/media.hpp>
|
||||
|
||||
#include <nostalgia/core/gfx.hpp>
|
||||
#include "commands/addcolorcommand.hpp"
|
||||
#include "commands/applycolorallpagescommand.hpp"
|
||||
#include "commands/duplicatepagecommand.hpp"
|
||||
#include "commands/movecolorcommand.hpp"
|
||||
#include "commands/removecolorcommand.hpp"
|
||||
#include "commands/removepagecommand.hpp"
|
||||
#include "commands/renamepagecommand.hpp"
|
||||
#include "commands/updatecolorcommand.hpp"
|
||||
#include "commands/updatecolorinfocommand.hpp"
|
||||
|
||||
#include "paletteeditor.hpp"
|
||||
#include "paletteeditor-imgui.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
PaletteEditorImGui::PaletteEditorImGui(studio::StudioContext &sctx, ox::CRStringView path):
|
||||
Editor(path),
|
||||
m_sctx(sctx),
|
||||
m_tctx(sctx.tctx),
|
||||
m_pal(*keel::readObj<Palette>(keelCtx(m_tctx), ox::FileAddress(itemPath())).unwrapThrow()) {
|
||||
undoStack()->changeTriggered.connect(this, &PaletteEditorImGui::handleCommand);
|
||||
}
|
||||
namespace ig = studio::ig;
|
||||
|
||||
void PaletteEditorImGui::keyStateChanged(turbine::Key key, bool down) {
|
||||
if (!down) {
|
||||
|
||||
void PaletteEditorImGui::PageRenameDialog::draw(turbine::Context &tctx) noexcept {
|
||||
if (!m_show) {
|
||||
return;
|
||||
}
|
||||
if (key >= turbine::Key::Num_1 && key <= turbine::Key::Num_9) {
|
||||
if (turbine::buttonDown(m_tctx, turbine::Key::Mod_Alt)) {
|
||||
m_page = ox::min<std::size_t>(
|
||||
static_cast<uint32_t>(key - turbine::Key::Num_1), m_pal.pages.size() - 1);
|
||||
}
|
||||
} else if (key == turbine::Key::Num_0) {
|
||||
if (turbine::buttonDown(m_tctx, turbine::Key::Mod_Alt)) {
|
||||
m_selectedColorRow =
|
||||
ox::min<std::size_t>(
|
||||
static_cast<uint32_t>(key - turbine::Key::Num_1 + 9), m_pal.pages.size() - 1);
|
||||
if (ig::BeginPopup(tctx, "Rename Page", m_show)) {
|
||||
ig::InputText("Name", m_name);
|
||||
switch (ig::PopupControlsOkCancel(m_show)) {
|
||||
case ig::PopupResponse::OK:
|
||||
inputSubmitted.emit(m_name);
|
||||
[[fallthrough]];
|
||||
case ig::PopupResponse::Cancel:
|
||||
close();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
PaletteEditorImGui::PaletteEditorImGui(studio::StudioContext &sctx, ox::StringParam path):
|
||||
Editor(std::move(path)),
|
||||
m_sctx(sctx),
|
||||
m_tctx(sctx.tctx),
|
||||
m_pal(*keel::readObj<Palette>(keelCtx(m_tctx), itemPath()).unwrapThrow()) {
|
||||
undoStack()->changeTriggered.connect(this, &PaletteEditorImGui::handleCommand);
|
||||
m_pageRenameDlg.inputSubmitted.connect(this, &PaletteEditorImGui::renamePage);
|
||||
}
|
||||
|
||||
void PaletteEditorImGui::draw(studio::StudioContext&) noexcept {
|
||||
auto const paneSize = ImGui::GetContentRegionAvail();
|
||||
{
|
||||
ImGui::BeginChild("Pages", ImVec2(250, paneSize.y), true);
|
||||
ImGui::BeginChild("Pages", {280, paneSize.y}, true);
|
||||
drawPagesEditor();
|
||||
ImGui::EndChild();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
{
|
||||
ImGui::BeginChild("Colors", ImVec2(-1, paneSize.y), true);
|
||||
ImGui::BeginChild("Colors", {-1, paneSize.y}, true);
|
||||
drawColorsEditor();
|
||||
ImGui::EndChild();
|
||||
}
|
||||
m_pageRenameDlg.draw(m_tctx);
|
||||
}
|
||||
|
||||
ox::Error PaletteEditorImGui::saveItem() noexcept {
|
||||
return m_sctx.project->writeObj(itemPath(), m_pal);
|
||||
return m_sctx.project->writeObj(itemPath(), m_pal, ox::ClawFormat::Organic);
|
||||
}
|
||||
|
||||
void PaletteEditorImGui::drawColumnLeftAlign(ox::CStringView txt) noexcept {
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%s", txt.c_str());
|
||||
}
|
||||
|
||||
void PaletteEditorImGui::drawColumn(ox::CStringView txt) noexcept {
|
||||
@@ -68,45 +84,61 @@ void PaletteEditorImGui::drawColumn(ox::CStringView txt) noexcept {
|
||||
ImGui::Text("%s", txt.c_str());
|
||||
}
|
||||
|
||||
void PaletteEditorImGui::numShortcuts(size_t &val, size_t const sizeRange) noexcept {
|
||||
auto const lastElem = sizeRange - 1;
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_0)) {
|
||||
val = ox::min<size_t>(9, lastElem);
|
||||
} else for (auto i = 9u; i < 10; --i) {
|
||||
auto const key = static_cast<ImGuiKey>(ImGuiKey_1 + i);
|
||||
if (ImGui::IsKeyPressed(key)) {
|
||||
val = ox::min<size_t>(i, lastElem);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PaletteEditorImGui::colorInput(ox::CStringView label, int &v, bool &inputFocused) noexcept {
|
||||
ImGui::InputInt(label.c_str(), &v, 1, 5);
|
||||
inputFocused = inputFocused || ImGui::IsItemFocused();
|
||||
v = ox::max(v, 0);
|
||||
}
|
||||
|
||||
void PaletteEditorImGui::drawColorsEditor() noexcept {
|
||||
constexpr auto tableFlags = ImGuiTableFlags_RowBg;
|
||||
auto const colorsSz = ImGui::GetContentRegionAvail();
|
||||
auto const colorEditor = m_selectedColorRow < colors(m_pal, m_page);
|
||||
auto const colorEditor = m_selectedColorRow < colorCnt(m_pal, m_page);
|
||||
auto const colorEditorWidth = 220;
|
||||
static constexpr auto toolbarHeight = 40;
|
||||
{
|
||||
auto const sz = ImVec2(70, 24);
|
||||
auto constexpr sz = ImVec2{70, 24};
|
||||
if (ImGui::Button("Add", sz)) {
|
||||
auto const colorSz = static_cast<int>(colors(m_pal, m_page));
|
||||
auto const colorSz = colorCnt(m_pal, m_page);
|
||||
constexpr Color16 c = 0;
|
||||
undoStack()->push(ox::make_unique<AddColorCommand>(&m_pal, c, m_page, colorSz));
|
||||
std::ignore = pushCommand<AddColorCommand>(m_pal, c, colorSz);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::BeginDisabled(m_selectedColorRow >= colors(m_pal, m_page));
|
||||
ImGui::BeginDisabled(m_selectedColorRow >= colorCnt(m_pal, m_page));
|
||||
{
|
||||
if (ImGui::Button("Remove", sz)) {
|
||||
undoStack()->push(
|
||||
ox::make_unique<RemoveColorCommand>(
|
||||
&m_pal,
|
||||
color(m_pal, m_page, static_cast<std::size_t>(m_selectedColorRow)),
|
||||
m_page,
|
||||
static_cast<int>(m_selectedColorRow)));
|
||||
m_selectedColorRow = ox::min(colors(m_pal, m_page) - 1, m_selectedColorRow);
|
||||
std::ignore = pushCommand<RemoveColorCommand>(m_pal, m_selectedColorRow);
|
||||
m_selectedColorRow = ox::min(colorCnt(m_pal, m_page) - 1, m_selectedColorRow);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
ImGui::BeginDisabled(m_selectedColorRow <= 0);
|
||||
{
|
||||
if (ImGui::Button("Move Up", sz)) {
|
||||
undoStack()->push(ox::make_unique<MoveColorCommand>(&m_pal, m_page, m_selectedColorRow, -1));
|
||||
std::ignore = pushCommand<MoveColorCommand>(
|
||||
m_pal, m_page, m_selectedColorRow, m_selectedColorRow - 1);
|
||||
--m_selectedColorRow;
|
||||
}
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
ImGui::SameLine();
|
||||
ImGui::BeginDisabled(m_selectedColorRow >= colors(m_pal, m_page) - 1);
|
||||
ImGui::BeginDisabled(m_selectedColorRow >= colorCnt(m_pal, m_page) - 1);
|
||||
{
|
||||
if (ImGui::Button("Move Down", sz)) {
|
||||
undoStack()->push(ox::make_unique<MoveColorCommand>(&m_pal, m_page, m_selectedColorRow, 1));
|
||||
std::ignore = pushCommand<MoveColorCommand>(
|
||||
m_pal, m_page, m_selectedColorRow, m_selectedColorRow + 1);
|
||||
++m_selectedColorRow;
|
||||
}
|
||||
}
|
||||
@@ -114,36 +146,45 @@ void PaletteEditorImGui::drawColorsEditor() noexcept {
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
auto const tblWidth = (colorsSz.x - colorEditorWidth - 8) * colorEditor;
|
||||
ImGui::BeginTable("Colors", 5, tableFlags, ImVec2(tblWidth, colorsSz.y - (toolbarHeight + 5)));
|
||||
auto const tblWidth = (colorsSz.x - static_cast<float>(colorEditorWidth) - 8.f)
|
||||
* static_cast<float>(colorEditor);
|
||||
ImGui::BeginTable(
|
||||
"Colors",
|
||||
6,
|
||||
tableFlags,
|
||||
{tblWidth, colorsSz.y - (toolbarHeight + 5)});
|
||||
{
|
||||
ImGui::TableSetupColumn("Idx", ImGuiTableColumnFlags_WidthFixed, 25);
|
||||
ImGui::TableSetupColumn("Red", ImGuiTableColumnFlags_WidthFixed, 50);
|
||||
ImGui::TableSetupColumn("Green", ImGuiTableColumnFlags_WidthFixed, 50);
|
||||
ImGui::TableSetupColumn("Blue", ImGuiTableColumnFlags_WidthFixed, 50);
|
||||
ImGui::TableSetupColumn("Color Preview", ImGuiTableColumnFlags_NoHide);
|
||||
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 100);
|
||||
ImGui::TableSetupColumn("Red", ImGuiTableColumnFlags_WidthFixed, 40);
|
||||
ImGui::TableSetupColumn("Green", ImGuiTableColumnFlags_WidthFixed, 40);
|
||||
ImGui::TableSetupColumn("Blue", ImGuiTableColumnFlags_WidthFixed, 40);
|
||||
ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_NoHide);
|
||||
ImGui::TableHeadersRow();
|
||||
for (auto i = 0u; auto const c : m_pal.pages[m_page]) {
|
||||
ImGui::PushID(static_cast<int>(i));
|
||||
ImGui::TableNextRow();
|
||||
drawColumn(i);
|
||||
drawColumn(red16(c));
|
||||
drawColumn(green16(c));
|
||||
drawColumn(blue16(c));
|
||||
ImGui::TableNextColumn();
|
||||
auto const ic = ImGui::GetColorU32(ImVec4(redf(c), greenf(c), bluef(c), 1));
|
||||
ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, ic);
|
||||
if (ImGui::Selectable("##ColorRow", i == m_selectedColorRow, ImGuiSelectableFlags_SpanAllColumns)) {
|
||||
m_selectedColorRow = i;
|
||||
if (m_page < m_pal.pages.size()) {
|
||||
for (auto i = 0u; auto const &c: m_pal.pages[m_page].colors) {
|
||||
ig::IDStackItem const idStackItem(static_cast<int>(i));
|
||||
ImGui::TableNextRow();
|
||||
drawColumn(i + 1);
|
||||
drawColumnLeftAlign(m_pal.colorNames[i]);
|
||||
drawColumn(red16(c));
|
||||
drawColumn(green16(c));
|
||||
drawColumn(blue16(c));
|
||||
ImGui::TableNextColumn();
|
||||
auto const ic = ImGui::GetColorU32({redf(c), greenf(c), bluef(c), 1});
|
||||
ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, ic);
|
||||
if (ImGui::Selectable(
|
||||
"##ColorRow", i == m_selectedColorRow, ImGuiSelectableFlags_SpanAllColumns)) {
|
||||
m_selectedColorRow = i;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
ImGui::PopID();
|
||||
++i;
|
||||
}
|
||||
}
|
||||
ImGui::EndTable();
|
||||
if (colorEditor) {
|
||||
ImGui::SameLine();
|
||||
ImGui::BeginChild("ColorEditor", ImVec2(colorEditorWidth, -1), true);
|
||||
ImGui::BeginChild("ColorEditor", {colorEditorWidth, -1}, true);
|
||||
drawColorEditor();
|
||||
ImGui::EndChild();
|
||||
}
|
||||
@@ -153,35 +194,42 @@ void PaletteEditorImGui::drawPagesEditor() noexcept {
|
||||
constexpr auto tableFlags = ImGuiTableFlags_RowBg;
|
||||
auto const paneSz = ImGui::GetContentRegionAvail();
|
||||
constexpr auto toolbarHeight = 40;
|
||||
auto const btnSz = ImVec2(paneSz.x / 3 - 5.5f, 24);
|
||||
auto const btnSz = ImVec2{paneSz.x / 4 - 5.5f, 24};
|
||||
if (ImGui::Button("Add", btnSz)) {
|
||||
undoStack()->push(ox::make_unique<AddPageCommand>(m_pal));
|
||||
std::ignore = pushCommand<DuplicatePageCommand>(m_pal, 0u, m_pal.pages.size());
|
||||
m_page = m_pal.pages.size() - 1;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Remove", btnSz)) {
|
||||
undoStack()->push(ox::make_unique<RemovePageCommand>(m_pal, m_page));
|
||||
std::ignore = pushCommand<RemovePageCommand>(m_pal, m_page);
|
||||
m_page = std::min(m_page, m_pal.pages.size() - 1);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Duplicate", btnSz)) {
|
||||
undoStack()->push(ox::make_unique<DuplicatePageCommand>(m_pal, m_page, m_pal.pages.size()));
|
||||
if (ImGui::Button("Clone", btnSz)) {
|
||||
std::ignore = pushCommand<DuplicatePageCommand>(m_pal, m_page, m_pal.pages.size());
|
||||
}
|
||||
ImGui::BeginTable("PageSelect", 2, tableFlags, ImVec2(paneSz.x, paneSz.y - (toolbarHeight + 5)));
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Rename", btnSz)) {
|
||||
m_pageRenameDlg.show(m_pal.pages[m_page].name);
|
||||
}
|
||||
ImGui::BeginTable(
|
||||
"PageSelect",
|
||||
2,
|
||||
tableFlags,
|
||||
{paneSz.x, paneSz.y - (toolbarHeight + 5)});
|
||||
{
|
||||
ImGui::TableSetupColumn("Page", ImGuiTableColumnFlags_WidthFixed, 60);
|
||||
ImGui::TableSetupColumn("Colors", ImGuiTableColumnFlags_NoHide);
|
||||
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 200);
|
||||
ImGui::TableHeadersRow();
|
||||
for (auto i = 0u; i < m_pal.pages.size(); ++i) {
|
||||
ImGui::PushID(static_cast<int>(i));
|
||||
ig::IDStackItem const idStackItem(static_cast<int>(i));
|
||||
ImGui::TableNextRow();
|
||||
drawColumn(i + 1);
|
||||
drawColumn(colors(m_pal, i));
|
||||
drawColumnLeftAlign(m_pal.pages[i].name);
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Selectable("##PageRow", i == m_page, ImGuiSelectableFlags_SpanAllColumns)) {
|
||||
m_page = i;
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
}
|
||||
ImGui::EndTable();
|
||||
@@ -193,20 +241,40 @@ void PaletteEditorImGui::drawColorEditor() noexcept {
|
||||
int g = green16(c);
|
||||
int b = blue16(c);
|
||||
int const a = alpha16(c);
|
||||
ImGui::InputInt("Red", &r, 1, 5);
|
||||
ImGui::InputInt("Green", &g, 1, 5);
|
||||
ImGui::InputInt("Blue", &b, 1, 5);
|
||||
auto const newName = ig::InputText<50>(
|
||||
"Name", m_pal.colorNames[m_selectedColorRow]);
|
||||
bool inputFocused = ImGui::IsItemFocused();
|
||||
ImGui::Separator();
|
||||
colorInput("Red", r, inputFocused);
|
||||
colorInput("Green", g, inputFocused);
|
||||
colorInput("Blue", b, inputFocused);
|
||||
if (ig::PushButton("Apply to all pages", {-1, ig::BtnSz.y})) {
|
||||
std::ignore = pushCommand<ApplyColorAllPagesCommand>(
|
||||
m_pal, m_page, m_selectedColorRow);
|
||||
}
|
||||
if (!inputFocused && !m_pageRenameDlg.isOpen()) {
|
||||
if (!ImGui::IsKeyDown(ImGuiKey_ModAlt)) {
|
||||
numShortcuts(m_selectedColorRow, largestPage(m_pal));
|
||||
} else {
|
||||
numShortcuts(m_page, m_pal.pages.size());
|
||||
}
|
||||
}
|
||||
auto const newColor = color16(r, g, b, a);
|
||||
if (c != newColor) {
|
||||
undoStack()->push(ox::make_unique<UpdateColorCommand>(
|
||||
&m_pal, m_page, static_cast<int>(m_selectedColorRow), c, newColor));
|
||||
std::ignore = pushCommand<UpdateColorCommand>(m_pal, m_page, m_selectedColorRow, newColor);
|
||||
}
|
||||
if (newName) {
|
||||
std::ignore = pushCommand<UpdateColorInfoCommand>(
|
||||
m_pal, m_selectedColorRow, static_cast<ox::String>(newName.text));
|
||||
}
|
||||
}
|
||||
|
||||
ox::Error PaletteEditorImGui::renamePage(ox::StringView name) noexcept {
|
||||
return pushCommand<RenamePageCommand>(m_pal, m_page, name);
|
||||
}
|
||||
|
||||
ox::Error PaletteEditorImGui::handleCommand(studio::UndoCommand const*cmd) noexcept {
|
||||
if (dynamic_cast<AddPageCommand const*>(cmd)) {
|
||||
m_page = m_pal.pages.size() - 1;
|
||||
} else if (dynamic_cast<RemovePageCommand const*>(cmd)) {
|
||||
if (dynamic_cast<RemovePageCommand const*>(cmd)) {
|
||||
m_page = ox::min(m_page, m_pal.pages.size() - 1);
|
||||
} else if (auto const dupPageCmd = dynamic_cast<DuplicatePageCommand const*>(cmd)) {
|
||||
m_page = ox::clamp<size_t>(dupPageCmd->insertIdx(), 0, m_pal.pages.size() - 1);
|
||||
|
||||
@@ -14,6 +14,23 @@ namespace nostalgia::core {
|
||||
class PaletteEditorImGui: public studio::Editor {
|
||||
|
||||
private:
|
||||
class PageRenameDialog {
|
||||
private:
|
||||
ox::IString<50> m_name;
|
||||
bool m_show = false;
|
||||
public:
|
||||
ox::Signal<ox::Error(ox::StringView name)> inputSubmitted;
|
||||
constexpr void show(ox::StringView const&name) noexcept {
|
||||
m_show = true;
|
||||
m_name = name;
|
||||
}
|
||||
constexpr void close() noexcept {
|
||||
m_show = false;
|
||||
}
|
||||
[[nodiscard]]
|
||||
constexpr bool isOpen() const noexcept { return m_show; }
|
||||
void draw(turbine::Context &tctx) noexcept;
|
||||
} m_pageRenameDlg;
|
||||
studio::StudioContext &m_sctx;
|
||||
turbine::Context &m_tctx;
|
||||
Palette m_pal;
|
||||
@@ -21,9 +38,7 @@ class PaletteEditorImGui: public studio::Editor {
|
||||
size_t m_page = 0;
|
||||
|
||||
public:
|
||||
PaletteEditorImGui(studio::StudioContext &sctx, ox::CRStringView path);
|
||||
|
||||
void keyStateChanged(turbine::Key key, bool down) override;
|
||||
PaletteEditorImGui(studio::StudioContext &sctx, ox::StringParam path);
|
||||
|
||||
void draw(studio::StudioContext&) noexcept final;
|
||||
|
||||
@@ -31,18 +46,26 @@ class PaletteEditorImGui: public studio::Editor {
|
||||
ox::Error saveItem() noexcept final;
|
||||
|
||||
private:
|
||||
static void drawColumnLeftAlign(ox::CStringView txt) noexcept;
|
||||
|
||||
static void drawColumn(ox::CStringView txt) noexcept;
|
||||
|
||||
static void drawColumn(ox::Integer_c auto i) noexcept {
|
||||
drawColumn(ox::itoa(i));
|
||||
}
|
||||
|
||||
static void numShortcuts(size_t &val, size_t sizeRange) noexcept;
|
||||
|
||||
static void colorInput(ox::CStringView label, int &v, bool &inputFocused) noexcept;
|
||||
|
||||
void drawColorsEditor() noexcept;
|
||||
|
||||
void drawPagesEditor() noexcept;
|
||||
|
||||
void drawColorEditor() noexcept;
|
||||
|
||||
ox::Error renamePage(ox::StringView name) noexcept;
|
||||
|
||||
ox::Error handleCommand(studio::UndoCommand const*) noexcept;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,178 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include "paletteeditor.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
AddPageCommand::AddPageCommand(Palette &pal) noexcept:
|
||||
m_pal(pal) {
|
||||
}
|
||||
|
||||
int AddPageCommand::commandId() const noexcept {
|
||||
return static_cast<int>(PaletteEditorCommandId::AddPage);
|
||||
}
|
||||
|
||||
void AddPageCommand::redo() noexcept {
|
||||
m_pal.pages.emplace_back();
|
||||
}
|
||||
|
||||
void AddPageCommand::undo() noexcept {
|
||||
std::ignore = m_pal.pages.erase(static_cast<std::size_t>(m_pal.pages.size() - 1));
|
||||
}
|
||||
|
||||
|
||||
DuplicatePageCommand::DuplicatePageCommand(Palette &pal, size_t srcIdx, size_t dstIdx) noexcept:
|
||||
m_pal(pal),
|
||||
m_dstIdx(dstIdx) {
|
||||
auto const&src = m_pal.pages[srcIdx];
|
||||
m_page.reserve(src.size());
|
||||
for (auto const&s : src) {
|
||||
m_page.emplace_back(s);
|
||||
}
|
||||
}
|
||||
|
||||
int DuplicatePageCommand::commandId() const noexcept {
|
||||
return static_cast<int>(PaletteEditorCommandId::DuplicatePage);
|
||||
}
|
||||
|
||||
void DuplicatePageCommand::redo() noexcept {
|
||||
m_pal.pages.emplace(m_dstIdx, std::move(m_page));
|
||||
}
|
||||
|
||||
void DuplicatePageCommand::undo() noexcept {
|
||||
m_page = std::move(m_pal.pages[m_dstIdx]);
|
||||
std::ignore = m_pal.pages.erase(static_cast<std::size_t>(m_dstIdx));
|
||||
}
|
||||
|
||||
size_t DuplicatePageCommand::insertIdx() const noexcept {
|
||||
return m_dstIdx;
|
||||
}
|
||||
|
||||
|
||||
RemovePageCommand::RemovePageCommand(Palette &pal, size_t idx) noexcept:
|
||||
m_pal(pal),
|
||||
m_idx(idx) {
|
||||
}
|
||||
|
||||
int RemovePageCommand::commandId() const noexcept {
|
||||
return static_cast<int>(PaletteEditorCommandId::RemovePage);
|
||||
}
|
||||
|
||||
void RemovePageCommand::redo() noexcept {
|
||||
m_page = std::move(m_pal.pages[m_idx]);
|
||||
std::ignore = m_pal.pages.erase(static_cast<std::size_t>(m_idx));
|
||||
}
|
||||
|
||||
void RemovePageCommand::undo() noexcept {
|
||||
m_pal.pages.insert(m_idx, std::move(m_page));
|
||||
}
|
||||
|
||||
|
||||
AddColorCommand::AddColorCommand(Palette *pal, Color16 color, size_t page, int idx) noexcept {
|
||||
m_pal = pal;
|
||||
m_color = color;
|
||||
m_page = page;
|
||||
m_idx = idx;
|
||||
}
|
||||
|
||||
int AddColorCommand::commandId() const noexcept {
|
||||
return static_cast<int>(PaletteEditorCommandId::AddColor);
|
||||
}
|
||||
|
||||
void AddColorCommand::redo() noexcept {
|
||||
m_pal->pages[m_page].insert(static_cast<std::size_t>(m_idx), m_color);
|
||||
}
|
||||
|
||||
void AddColorCommand::undo() noexcept {
|
||||
std::ignore = m_pal->pages[m_page].erase(static_cast<std::size_t>(m_idx));
|
||||
}
|
||||
|
||||
|
||||
RemoveColorCommand::RemoveColorCommand(Palette *pal, Color16 color, size_t page, int idx) noexcept {
|
||||
m_pal = pal;
|
||||
m_color = color;
|
||||
m_page = page;
|
||||
m_idx = idx;
|
||||
}
|
||||
|
||||
int RemoveColorCommand::commandId() const noexcept {
|
||||
return static_cast<int>(PaletteEditorCommandId::RemoveColor);
|
||||
}
|
||||
|
||||
void RemoveColorCommand::redo() noexcept {
|
||||
std::ignore = m_pal->pages[m_page].erase(static_cast<std::size_t>(m_idx));
|
||||
}
|
||||
|
||||
void RemoveColorCommand::undo() noexcept {
|
||||
m_pal->pages[m_page].insert(static_cast<std::size_t>(m_idx), m_color);
|
||||
}
|
||||
|
||||
|
||||
UpdateColorCommand::UpdateColorCommand(
|
||||
Palette *pal,
|
||||
size_t page,
|
||||
int idx,
|
||||
Color16 oldColor,
|
||||
Color16 newColor) noexcept {
|
||||
m_pal = pal;
|
||||
m_page = page;
|
||||
m_idx = idx;
|
||||
m_oldColor = oldColor;
|
||||
m_newColor = newColor;
|
||||
//setObsolete(m_oldColor == m_newColor);
|
||||
}
|
||||
|
||||
bool UpdateColorCommand::mergeWith(const UndoCommand *cmd) noexcept {
|
||||
if (cmd->commandId() != static_cast<int>(PaletteEditorCommandId::UpdateColor)) {
|
||||
return false;
|
||||
}
|
||||
auto ucCmd = static_cast<const UpdateColorCommand*>(cmd);
|
||||
if (m_idx != ucCmd->m_idx) {
|
||||
return false;
|
||||
}
|
||||
m_newColor = ucCmd->m_newColor;
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
int UpdateColorCommand::commandId() const noexcept {
|
||||
return static_cast<int>(PaletteEditorCommandId::UpdateColor);
|
||||
}
|
||||
|
||||
void UpdateColorCommand::redo() noexcept {
|
||||
m_pal->pages[m_page][static_cast<std::size_t>(m_idx)] = m_newColor;
|
||||
}
|
||||
|
||||
void UpdateColorCommand::undo() noexcept {
|
||||
m_pal->pages[m_page][static_cast<std::size_t>(m_idx)] = m_oldColor;
|
||||
}
|
||||
|
||||
|
||||
MoveColorCommand::MoveColorCommand(Palette *pal, size_t page, std::size_t idx, int offset) noexcept {
|
||||
m_pal = pal;
|
||||
m_page = page;
|
||||
m_idx = idx;
|
||||
m_offset = offset;
|
||||
}
|
||||
|
||||
int MoveColorCommand::commandId() const noexcept {
|
||||
return static_cast<int>(PaletteEditorCommandId::MoveColor);
|
||||
}
|
||||
|
||||
void MoveColorCommand::redo() noexcept {
|
||||
moveColor(static_cast<int>(m_idx), m_offset);
|
||||
}
|
||||
|
||||
void MoveColorCommand::undo() noexcept {
|
||||
moveColor(static_cast<int>(m_idx) + m_offset, -m_offset);
|
||||
}
|
||||
|
||||
void MoveColorCommand::moveColor(int idx, int offset) noexcept {
|
||||
const auto c = m_pal->pages[m_page][static_cast<std::size_t>(idx)];
|
||||
std::ignore = m_pal->pages[m_page].erase(static_cast<std::size_t>(idx));
|
||||
m_pal->pages[m_page].insert(static_cast<std::size_t>(idx + offset), c);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,178 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <studio/studio.hpp>
|
||||
|
||||
#include <nostalgia/core/color.hpp>
|
||||
#include <nostalgia/core/gfx.hpp>
|
||||
#include <nostalgia/core/palette.hpp>
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
enum class PaletteEditorCommandId {
|
||||
AddPage,
|
||||
DuplicatePage,
|
||||
RemovePage,
|
||||
AddColor,
|
||||
RemoveColor,
|
||||
UpdateColor,
|
||||
MoveColor,
|
||||
};
|
||||
|
||||
|
||||
class AddPageCommand: public studio::UndoCommand {
|
||||
private:
|
||||
Palette &m_pal;
|
||||
|
||||
public:
|
||||
AddPageCommand(Palette &pal) noexcept;
|
||||
|
||||
~AddPageCommand() noexcept override = default;
|
||||
|
||||
[[nodiscard]]
|
||||
int commandId() const noexcept final;
|
||||
|
||||
void redo() noexcept final;
|
||||
|
||||
void undo() noexcept final;
|
||||
|
||||
};
|
||||
|
||||
class DuplicatePageCommand: public studio::UndoCommand {
|
||||
private:
|
||||
Palette &m_pal;
|
||||
size_t m_dstIdx = 0;
|
||||
ox::Vector<Color16> m_page;
|
||||
|
||||
public:
|
||||
DuplicatePageCommand(Palette &pal, size_t srcIdx, size_t dstIdx) noexcept;
|
||||
|
||||
~DuplicatePageCommand() noexcept override = default;
|
||||
|
||||
[[nodiscard]]
|
||||
int commandId() const noexcept final;
|
||||
|
||||
void redo() noexcept final;
|
||||
|
||||
void undo() noexcept final;
|
||||
|
||||
[[nodiscard]]
|
||||
size_t insertIdx() const noexcept;
|
||||
|
||||
};
|
||||
|
||||
class RemovePageCommand: public studio::UndoCommand {
|
||||
private:
|
||||
Palette &m_pal;
|
||||
size_t m_idx = 0;
|
||||
ox::Vector<Color16> m_page;
|
||||
|
||||
public:
|
||||
RemovePageCommand(Palette &pal, size_t idx) noexcept;
|
||||
|
||||
~RemovePageCommand() noexcept override = default;
|
||||
|
||||
[[nodiscard]]
|
||||
int commandId() const noexcept final;
|
||||
|
||||
void redo() noexcept final;
|
||||
|
||||
void undo() noexcept final;
|
||||
|
||||
};
|
||||
|
||||
class AddColorCommand: public studio::UndoCommand {
|
||||
private:
|
||||
Palette *m_pal = nullptr;
|
||||
Color16 m_color = 0;
|
||||
int m_idx = -1;
|
||||
size_t m_page = 0;
|
||||
|
||||
public:
|
||||
AddColorCommand(Palette *pal, Color16 color, size_t page, int idx) noexcept;
|
||||
|
||||
~AddColorCommand() noexcept override = default;
|
||||
|
||||
[[nodiscard]]
|
||||
int commandId() const noexcept override;
|
||||
|
||||
void redo() noexcept override;
|
||||
|
||||
void undo() noexcept override;
|
||||
|
||||
};
|
||||
|
||||
class RemoveColorCommand: public studio::UndoCommand {
|
||||
private:
|
||||
Palette *m_pal = nullptr;
|
||||
Color16 m_color = 0;
|
||||
size_t m_page = 0;
|
||||
int m_idx = -1;
|
||||
|
||||
public:
|
||||
RemoveColorCommand(Palette *pal, Color16 color, size_t page, int idx) noexcept;
|
||||
|
||||
~RemoveColorCommand() noexcept override = default;
|
||||
|
||||
[[nodiscard]]
|
||||
int commandId() const noexcept override;
|
||||
|
||||
void redo() noexcept override;
|
||||
|
||||
void undo() noexcept override;
|
||||
|
||||
};
|
||||
|
||||
class UpdateColorCommand: public studio::UndoCommand {
|
||||
private:
|
||||
Palette *m_pal = nullptr;
|
||||
Color16 m_oldColor = 0;
|
||||
Color16 m_newColor = 0;
|
||||
size_t m_page = 0;
|
||||
int m_idx = -1;
|
||||
|
||||
public:
|
||||
UpdateColorCommand(Palette *pal, size_t page, int idx, Color16 oldColor, Color16 newColor) noexcept;
|
||||
|
||||
~UpdateColorCommand() noexcept override = default;
|
||||
|
||||
[[nodiscard]]
|
||||
bool mergeWith(const UndoCommand *cmd) noexcept final;
|
||||
|
||||
[[nodiscard]]
|
||||
int commandId() const noexcept final;
|
||||
|
||||
void redo() noexcept final;
|
||||
|
||||
void undo() noexcept final;
|
||||
|
||||
};
|
||||
|
||||
class MoveColorCommand: public studio::UndoCommand {
|
||||
private:
|
||||
Palette *m_pal = nullptr;
|
||||
size_t m_page = 0;
|
||||
std::size_t m_idx = 0;
|
||||
int m_offset = 0;
|
||||
|
||||
public:
|
||||
MoveColorCommand(Palette *pal, size_t page, std::size_t idx, int offset) noexcept;
|
||||
|
||||
~MoveColorCommand() noexcept override = default;
|
||||
|
||||
[[nodiscard]]
|
||||
int commandId() const noexcept override;
|
||||
|
||||
public:
|
||||
void redo() noexcept override;
|
||||
|
||||
void undo() noexcept override;
|
||||
|
||||
private:
|
||||
void moveColor(int idx, int offset) noexcept;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -25,7 +25,7 @@ static class: public studio::Module {
|
||||
out.emplace_back(ox::make<studio::ItemMakerT<core::Palette>>("Palette", "Palettes", FileExt_npal));
|
||||
return out;
|
||||
}
|
||||
} mod;
|
||||
} const mod;
|
||||
|
||||
const studio::Module *studioModule() noexcept {
|
||||
return &mod;
|
||||
|
||||
@@ -24,7 +24,7 @@ AddSubSheetCommand::AddSubSheetCommand(
|
||||
}
|
||||
}
|
||||
|
||||
void AddSubSheetCommand::redo() noexcept {
|
||||
ox::Error AddSubSheetCommand::redo() noexcept {
|
||||
auto &parent = getSubSheet(m_img, m_parentIdx);
|
||||
if (m_addedSheets.size() < 2) {
|
||||
auto i = parent.subsheets.size();
|
||||
@@ -35,9 +35,10 @@ void AddSubSheetCommand::redo() noexcept {
|
||||
parent.columns = 0;
|
||||
parent.subsheets.emplace_back(m_img.idIt++, "Subsheet 1", 1, 1, m_img.bpp);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void AddSubSheetCommand::undo() noexcept {
|
||||
ox::Error AddSubSheetCommand::undo() noexcept {
|
||||
auto &parent = getSubSheet(m_img, m_parentIdx);
|
||||
if (parent.subsheets.size() == 2) {
|
||||
auto s = parent.subsheets[0];
|
||||
@@ -45,11 +46,14 @@ void AddSubSheetCommand::undo() noexcept {
|
||||
parent.columns = s.columns;
|
||||
parent.pixels = std::move(s.pixels);
|
||||
parent.subsheets.clear();
|
||||
--m_img.idIt;
|
||||
} else {
|
||||
for (auto idx = m_addedSheets.rbegin(); idx != m_addedSheets.rend(); ++idx) {
|
||||
oxLogError(rmSubSheet(m_img, *idx));
|
||||
OX_RETURN_ERROR(rmSubSheet(m_img, *idx));
|
||||
--m_img.idIt;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
int AddSubSheetCommand::commandId() const noexcept {
|
||||
|
||||
@@ -17,9 +17,9 @@ class AddSubSheetCommand: public TileSheetCommand {
|
||||
public:
|
||||
AddSubSheetCommand(TileSheet &img, TileSheet::SubSheetIdx parentIdx) noexcept;
|
||||
|
||||
void redo() noexcept final;
|
||||
ox::Error redo() noexcept final;
|
||||
|
||||
void undo() noexcept final;
|
||||
ox::Error undo() noexcept final;
|
||||
|
||||
[[nodiscard]]
|
||||
int commandId() const noexcept final;
|
||||
|
||||
@@ -12,7 +12,7 @@ TileSheetClipboard::Pixel::Pixel(uint16_t pColorIdx, ox::Point pPt) noexcept {
|
||||
}
|
||||
|
||||
|
||||
void TileSheetClipboard::addPixel(const ox::Point &pt, uint16_t colorIdx) noexcept {
|
||||
void TileSheetClipboard::addPixel(ox::Point const&pt, uint16_t colorIdx) noexcept {
|
||||
m_pixels.emplace_back(colorIdx, pt);
|
||||
}
|
||||
|
||||
@@ -25,34 +25,38 @@ CutPasteCommand::CutPasteCommand(
|
||||
CommandId commandId,
|
||||
TileSheet &img,
|
||||
TileSheet::SubSheetIdx subSheetIdx,
|
||||
const ox::Point &dstStart,
|
||||
const ox::Point &dstEnd,
|
||||
const TileSheetClipboard &cb) noexcept:
|
||||
ox::Point const&dstStart,
|
||||
ox::Point dstEnd,
|
||||
TileSheetClipboard const&cb) noexcept:
|
||||
m_commandId(commandId),
|
||||
m_img(img),
|
||||
m_subSheetIdx(std::move(subSheetIdx)) {
|
||||
const auto &subsheet = getSubSheet(m_img, m_subSheetIdx);
|
||||
for (const auto &p : cb.pixels()) {
|
||||
const auto dstPt = p.pt + dstStart;
|
||||
auto const&ss = getSubSheet(m_img, m_subSheetIdx);
|
||||
dstEnd.x = std::min(ss.columns * TileWidth - 1, dstEnd.x);
|
||||
dstEnd.y = std::min(ss.rows * TileHeight - 1, dstEnd.y);
|
||||
for (auto const&p : cb.pixels()) {
|
||||
auto const dstPt = p.pt + dstStart;
|
||||
if (dstPt.x <= dstEnd.x && dstPt.y <= dstEnd.y) {
|
||||
const auto idx = core::idx(subsheet, dstPt);
|
||||
m_changes.emplace_back(static_cast<uint32_t>(idx), p.colorIdx, getPixel(subsheet, m_img.bpp, idx));
|
||||
auto const idx = core::idx(ss, dstPt);
|
||||
m_changes.emplace_back(static_cast<uint32_t>(idx), p.colorIdx, getPixel(ss, m_img.bpp, idx));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CutPasteCommand::redo() noexcept {
|
||||
ox::Error CutPasteCommand::redo() noexcept {
|
||||
auto &subsheet = getSubSheet(m_img, m_subSheetIdx);
|
||||
for (const auto &c : m_changes) {
|
||||
setPixel(subsheet, m_img.bpp, c.idx, static_cast<uint8_t>(c.newPalIdx));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void CutPasteCommand::undo() noexcept {
|
||||
ox::Error CutPasteCommand::undo() noexcept {
|
||||
auto &subsheet = getSubSheet(m_img, m_subSheetIdx);
|
||||
for (const auto &c : m_changes) {
|
||||
setPixel(subsheet, m_img.bpp, c.idx, static_cast<uint8_t>(c.oldPalIdx));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
int CutPasteCommand::commandId() const noexcept {
|
||||
|
||||
@@ -10,12 +10,14 @@
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
OX_MODEL_FWD_DECL(class TileSheetClipboard);
|
||||
|
||||
class TileSheetClipboard: public turbine::ClipboardObject<TileSheetClipboard> {
|
||||
public:
|
||||
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.studio.TileSheetClipboard";
|
||||
static constexpr auto TypeVersion = 1;
|
||||
|
||||
oxModelFriend(TileSheetClipboard);
|
||||
OX_MODEL_FRIEND(TileSheetClipboard);
|
||||
|
||||
struct Pixel {
|
||||
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.studio.TileSheetClipboard.Pixel";
|
||||
@@ -34,14 +36,14 @@ class TileSheetClipboard: public turbine::ClipboardObject<TileSheetClipboard> {
|
||||
ox::Vector<Pixel> const&pixels() const noexcept;
|
||||
};
|
||||
|
||||
oxModelBegin(TileSheetClipboard::Pixel)
|
||||
oxModelField(colorIdx)
|
||||
oxModelField(pt)
|
||||
oxModelEnd()
|
||||
OX_MODEL_BEGIN(TileSheetClipboard::Pixel)
|
||||
OX_MODEL_FIELD(colorIdx)
|
||||
OX_MODEL_FIELD(pt)
|
||||
OX_MODEL_END()
|
||||
|
||||
oxModelBegin(TileSheetClipboard)
|
||||
oxModelFieldRename(m_pixels, pixels)
|
||||
oxModelEnd()
|
||||
OX_MODEL_BEGIN(TileSheetClipboard)
|
||||
OX_MODEL_FIELD_RENAME(m_pixels, pixels)
|
||||
OX_MODEL_END()
|
||||
|
||||
class CutPasteCommand: public TileSheetCommand {
|
||||
private:
|
||||
@@ -66,12 +68,12 @@ class CutPasteCommand: public TileSheetCommand {
|
||||
TileSheet &img,
|
||||
TileSheet::SubSheetIdx subSheetIdx,
|
||||
ox::Point const&dstStart,
|
||||
ox::Point const&dstEnd,
|
||||
ox::Point dstEnd,
|
||||
TileSheetClipboard const&cb) noexcept;
|
||||
|
||||
void redo() noexcept final;
|
||||
ox::Error redo() noexcept final;
|
||||
|
||||
void undo() noexcept final;
|
||||
ox::Error undo() noexcept final;
|
||||
|
||||
[[nodiscard]]
|
||||
int commandId() const noexcept final;
|
||||
@@ -81,4 +83,4 @@ class CutPasteCommand: public TileSheetCommand {
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
* Copyright 2016 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <ox/std/algorithm.hpp>
|
||||
|
||||
#include "deletetilescommand.hpp"
|
||||
|
||||
namespace nostalgia::core {
|
||||
@@ -20,34 +22,34 @@ core::DeleteTilesCommand::DeleteTilesCommand(
|
||||
// copy pixels to be erased
|
||||
{
|
||||
auto &s = getSubSheet(m_img, m_idx);
|
||||
auto &p = s.pixels;
|
||||
auto dst = m_deletedPixels.data();
|
||||
auto src = p.data() + m_deletePos;
|
||||
const auto sz = m_deleteSz * sizeof(decltype(p[0]));
|
||||
ox::memcpy(dst, src, sz);
|
||||
auto dst = m_deletedPixels.begin();
|
||||
auto src = s.pixels.begin() + m_deletePos;
|
||||
ox::copy_n(src, m_deleteSz, dst);
|
||||
}
|
||||
}
|
||||
|
||||
void core::DeleteTilesCommand::redo() noexcept {
|
||||
ox::Error core::DeleteTilesCommand::redo() noexcept {
|
||||
auto &s = getSubSheet(m_img, m_idx);
|
||||
auto &p = s.pixels;
|
||||
auto srcPos = m_deletePos + m_deleteSz;
|
||||
const auto src = p.data() + srcPos;
|
||||
const auto dst1 = p.data() + m_deletePos;
|
||||
const auto dst2 = p.data() + (p.size() - m_deleteSz);
|
||||
auto const src = &p[srcPos];
|
||||
auto const dst1 = &p[m_deletePos];
|
||||
auto const dst2 = &p[(p.size() - m_deleteSz)];
|
||||
ox::memmove(dst1, src, p.size() - srcPos);
|
||||
ox::memset(dst2, 0, m_deleteSz * sizeof(decltype(p[0])));
|
||||
return {};
|
||||
}
|
||||
|
||||
void DeleteTilesCommand::undo() noexcept {
|
||||
ox::Error DeleteTilesCommand::undo() noexcept {
|
||||
auto &s = getSubSheet(m_img, m_idx);
|
||||
auto &p = s.pixels;
|
||||
const auto src = p.data() + m_deletePos;
|
||||
const auto dst1 = p.data() + m_deletePos + m_deleteSz;
|
||||
const auto dst2 = src;
|
||||
const auto sz = p.size() - m_deletePos - m_deleteSz;
|
||||
auto const src = &p[m_deletePos];
|
||||
auto const dst1 = &p[m_deletePos + m_deleteSz];
|
||||
auto const dst2 = src;
|
||||
auto const sz = p.size() - m_deletePos - m_deleteSz;
|
||||
ox::memmove(dst1, src, sz);
|
||||
ox::memcpy(dst2, m_deletedPixels.data(), m_deletedPixels.size());
|
||||
return {};
|
||||
}
|
||||
|
||||
int DeleteTilesCommand::commandId() const noexcept {
|
||||
|
||||
@@ -23,9 +23,9 @@ class DeleteTilesCommand: public TileSheetCommand {
|
||||
std::size_t tileIdx,
|
||||
std::size_t tileCnt) noexcept;
|
||||
|
||||
void redo() noexcept final;
|
||||
ox::Error redo() noexcept final;
|
||||
|
||||
void undo() noexcept final;
|
||||
ox::Error undo() noexcept final;
|
||||
|
||||
[[nodiscard]]
|
||||
int commandId() const noexcept final;
|
||||
|
||||
@@ -21,13 +21,13 @@ DrawCommand::DrawCommand(
|
||||
DrawCommand::DrawCommand(
|
||||
TileSheet &img,
|
||||
TileSheet::SubSheetIdx subSheetIdx,
|
||||
const ox::Vector<std::size_t> &idxList,
|
||||
ox::Vector<std::size_t> const&idxList,
|
||||
int palIdx) noexcept:
|
||||
m_img(img),
|
||||
m_subSheetIdx(std::move(subSheetIdx)),
|
||||
m_palIdx(palIdx) {
|
||||
auto &subsheet = getSubSheet(m_img, m_subSheetIdx);
|
||||
for (const auto idx : idxList) {
|
||||
for (auto const idx : idxList) {
|
||||
m_changes.emplace_back(static_cast<uint32_t>(idx), getPixel(subsheet, m_img.bpp, idx));
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,7 @@ bool DrawCommand::append(std::size_t idx) noexcept {
|
||||
auto &subsheet = getSubSheet(m_img, m_subSheetIdx);
|
||||
if (m_changes.back().value->idx != idx && getPixel(subsheet, m_img.bpp, idx) != m_palIdx) {
|
||||
// duplicate entries are bad
|
||||
auto existing = ox::find_if(m_changes.cbegin(), m_changes.cend(), [idx](const auto &c) {
|
||||
auto existing = ox::find_if(m_changes.cbegin(), m_changes.cend(), [idx](auto const&c) {
|
||||
return c.idx == idx;
|
||||
});
|
||||
if (existing == m_changes.cend()) {
|
||||
@@ -48,7 +48,7 @@ bool DrawCommand::append(std::size_t idx) noexcept {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DrawCommand::append(const ox::Vector<std::size_t> &idxList) noexcept {
|
||||
bool DrawCommand::append(ox::Vector<std::size_t> const&idxList) noexcept {
|
||||
auto out = false;
|
||||
for (auto idx : idxList) {
|
||||
out = append(idx) || out;
|
||||
@@ -56,18 +56,20 @@ bool DrawCommand::append(const ox::Vector<std::size_t> &idxList) noexcept {
|
||||
return out;
|
||||
}
|
||||
|
||||
void DrawCommand::redo() noexcept {
|
||||
ox::Error DrawCommand::redo() noexcept {
|
||||
auto &subsheet = getSubSheet(m_img, m_subSheetIdx);
|
||||
for (const auto &c : m_changes) {
|
||||
for (auto const&c : m_changes) {
|
||||
setPixel(subsheet, m_img.bpp, c.idx, static_cast<uint8_t>(m_palIdx));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void DrawCommand::undo() noexcept {
|
||||
ox::Error DrawCommand::undo() noexcept {
|
||||
auto &subsheet = getSubSheet(m_img, m_subSheetIdx);
|
||||
for (const auto &c : m_changes) {
|
||||
for (auto const&c : m_changes) {
|
||||
setPixel(subsheet, m_img.bpp, c.idx, static_cast<uint8_t>(c.oldPalIdx));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
int DrawCommand::commandId() const noexcept {
|
||||
|
||||
@@ -33,16 +33,16 @@ class DrawCommand: public TileSheetCommand {
|
||||
DrawCommand(
|
||||
TileSheet &img,
|
||||
TileSheet::SubSheetIdx subSheetIdx,
|
||||
const ox::Vector<std::size_t> &idxList,
|
||||
ox::Vector<std::size_t> const&idxList,
|
||||
int palIdx) noexcept;
|
||||
|
||||
bool append(std::size_t idx) noexcept;
|
||||
|
||||
bool append(const ox::Vector<std::size_t> &idxList) noexcept;
|
||||
bool append(ox::Vector<std::size_t> const&idxList) noexcept;
|
||||
|
||||
void redo() noexcept final;
|
||||
ox::Error redo() noexcept final;
|
||||
|
||||
void undo() noexcept final;
|
||||
ox::Error undo() noexcept final;
|
||||
|
||||
[[nodiscard]]
|
||||
int commandId() const noexcept final;
|
||||
|
||||
@@ -21,33 +21,34 @@ core::InsertTilesCommand::InsertTilesCommand(
|
||||
{
|
||||
auto &s = getSubSheet(m_img, m_idx);
|
||||
auto &p = s.pixels;
|
||||
auto dst = m_deletedPixels.data();
|
||||
auto src = p.data() + p.size() - m_insertCnt;
|
||||
const auto sz = m_insertCnt * sizeof(decltype(p[0]));
|
||||
ox::memcpy(dst, src, sz);
|
||||
auto dst = m_deletedPixels.begin();
|
||||
auto src = p.begin() + p.size() - m_insertCnt;
|
||||
ox::copy_n(src, m_insertCnt, dst);
|
||||
}
|
||||
}
|
||||
|
||||
void InsertTilesCommand::redo() noexcept {
|
||||
ox::Error InsertTilesCommand::redo() noexcept {
|
||||
auto &s = getSubSheet(m_img, m_idx);
|
||||
auto &p = s.pixels;
|
||||
auto dstPos = m_insertPos + m_insertCnt;
|
||||
const auto dst = p.data() + dstPos;
|
||||
const auto src = p.data() + m_insertPos;
|
||||
auto const dst = &p[dstPos];
|
||||
auto const src = &p[m_insertPos];
|
||||
ox::memmove(dst, src, p.size() - dstPos);
|
||||
ox::memset(src, 0, m_insertCnt * sizeof(decltype(p[0])));
|
||||
return {};
|
||||
}
|
||||
|
||||
void InsertTilesCommand::undo() noexcept {
|
||||
ox::Error InsertTilesCommand::undo() noexcept {
|
||||
auto &s = getSubSheet(m_img, m_idx);
|
||||
auto &p = s.pixels;
|
||||
const auto srcIdx = m_insertPos + m_insertCnt;
|
||||
const auto src = p.data() + srcIdx;
|
||||
const auto dst1 = p.data() + m_insertPos;
|
||||
const auto dst2 = p.data() + p.size() - m_insertCnt;
|
||||
const auto sz = p.size() - srcIdx;
|
||||
auto const srcIdx = m_insertPos + m_insertCnt;
|
||||
auto const src = &p[srcIdx];
|
||||
auto const dst1 = &p[m_insertPos];
|
||||
auto const dst2 = &p[p.size() - m_insertCnt];
|
||||
auto const sz = p.size() - srcIdx;
|
||||
ox::memmove(dst1, src, sz);
|
||||
ox::memcpy(dst2, m_deletedPixels.data(), m_deletedPixels.size());
|
||||
return {};
|
||||
}
|
||||
|
||||
int InsertTilesCommand::commandId() const noexcept {
|
||||
|
||||
@@ -23,9 +23,9 @@ class InsertTilesCommand: public TileSheetCommand {
|
||||
std::size_t tileIdx,
|
||||
std::size_t tileCnt) noexcept;
|
||||
|
||||
void redo() noexcept final;
|
||||
ox::Error redo() noexcept final;
|
||||
|
||||
void undo() noexcept final;
|
||||
ox::Error undo() noexcept final;
|
||||
|
||||
[[nodiscard]]
|
||||
int commandId() const noexcept final;
|
||||
|
||||
@@ -9,19 +9,21 @@ namespace nostalgia::core {
|
||||
core::PaletteChangeCommand::PaletteChangeCommand(
|
||||
TileSheet::SubSheetIdx idx,
|
||||
TileSheet &img,
|
||||
ox::CRStringView newPalette) noexcept:
|
||||
ox::StringViewCR newPalette) noexcept:
|
||||
m_img(img),
|
||||
m_idx(std::move(idx)),
|
||||
m_oldPalette(m_img.defaultPalette),
|
||||
m_newPalette(ox::FileAddress(ox::sfmt<ox::IString<43>>("uuid://{}", newPalette))) {
|
||||
}
|
||||
|
||||
void PaletteChangeCommand::redo() noexcept {
|
||||
ox::Error PaletteChangeCommand::redo() noexcept {
|
||||
m_img.defaultPalette = m_newPalette;
|
||||
return {};
|
||||
}
|
||||
|
||||
void PaletteChangeCommand::undo() noexcept {
|
||||
ox::Error PaletteChangeCommand::undo() noexcept {
|
||||
m_img.defaultPalette = m_oldPalette;
|
||||
return {};
|
||||
}
|
||||
|
||||
int PaletteChangeCommand::commandId() const noexcept {
|
||||
|
||||
@@ -19,11 +19,11 @@ class PaletteChangeCommand: public TileSheetCommand {
|
||||
PaletteChangeCommand(
|
||||
TileSheet::SubSheetIdx idx,
|
||||
TileSheet &img,
|
||||
ox::CRStringView newPalette) noexcept;
|
||||
ox::StringViewCR newPalette) noexcept;
|
||||
|
||||
void redo() noexcept final;
|
||||
ox::Error redo() noexcept final;
|
||||
|
||||
void undo() noexcept final;
|
||||
ox::Error undo() noexcept final;
|
||||
|
||||
[[nodiscard]]
|
||||
int commandId() const noexcept final;
|
||||
|
||||
@@ -11,19 +11,20 @@ core::RmSubSheetCommand::RmSubSheetCommand(TileSheet &img, TileSheet::SubSheetId
|
||||
m_idx(std::move(idx)),
|
||||
m_parentIdx(m_idx) {
|
||||
m_parentIdx.pop_back();
|
||||
auto &parent = getSubSheet(m_img, m_parentIdx);
|
||||
m_sheet = parent.subsheets[*m_idx.back().value];
|
||||
}
|
||||
|
||||
void RmSubSheetCommand::redo() noexcept {
|
||||
ox::Error RmSubSheetCommand::redo() noexcept {
|
||||
auto &parent = getSubSheet(m_img, m_parentIdx);
|
||||
oxLogError(parent.subsheets.erase(*m_idx.back().value).error);
|
||||
m_sheet = std::move(parent.subsheets[*m_idx.back().value]);
|
||||
OX_RETURN_ERROR(parent.subsheets.erase(*m_idx.back().value).error);
|
||||
return {};
|
||||
}
|
||||
|
||||
void RmSubSheetCommand::undo() noexcept {
|
||||
ox::Error RmSubSheetCommand::undo() noexcept {
|
||||
auto &parent = getSubSheet(m_img, m_parentIdx);
|
||||
auto i = *m_idx.back().value;
|
||||
parent.subsheets.insert(i, m_sheet);
|
||||
auto const i = *m_idx.back().value;
|
||||
parent.subsheets.insert(i, std::move(m_sheet));
|
||||
return {};
|
||||
}
|
||||
|
||||
int RmSubSheetCommand::commandId() const noexcept {
|
||||
|
||||
@@ -18,9 +18,9 @@ class RmSubSheetCommand: public TileSheetCommand {
|
||||
public:
|
||||
RmSubSheetCommand(TileSheet &img, TileSheet::SubSheetIdx idx) noexcept;
|
||||
|
||||
void redo() noexcept final;
|
||||
ox::Error redo() noexcept final;
|
||||
|
||||
void undo() noexcept final;
|
||||
ox::Error undo() noexcept final;
|
||||
|
||||
[[nodiscard]]
|
||||
int commandId() const noexcept final;
|
||||
|
||||
@@ -20,17 +20,17 @@ core::UpdateSubSheetCommand::UpdateSubSheetCommand(
|
||||
m_newRows(rows) {
|
||||
}
|
||||
|
||||
void UpdateSubSheetCommand::redo() noexcept {
|
||||
ox::Error UpdateSubSheetCommand::redo() noexcept {
|
||||
auto &sheet = getSubSheet(m_img, m_idx);
|
||||
sheet.name = m_newName;
|
||||
sheet.columns = m_newCols;
|
||||
sheet.rows = m_newRows;
|
||||
oxLogError(setPixelCount(sheet, m_img.bpp, static_cast<std::size_t>(PixelsPerTile * m_newCols * m_newRows)));
|
||||
oxLogError(resizeSubsheet(sheet, m_img.bpp, {m_newCols, m_newRows}));
|
||||
return {};
|
||||
}
|
||||
|
||||
void UpdateSubSheetCommand::undo() noexcept {
|
||||
ox::Error UpdateSubSheetCommand::undo() noexcept {
|
||||
auto &sheet = getSubSheet(m_img, m_idx);
|
||||
sheet = m_sheet;
|
||||
return {};
|
||||
}
|
||||
|
||||
int UpdateSubSheetCommand::commandId() const noexcept {
|
||||
|
||||
@@ -25,9 +25,9 @@ class UpdateSubSheetCommand: public TileSheetCommand {
|
||||
int cols,
|
||||
int rows) noexcept;
|
||||
|
||||
void redo() noexcept final;
|
||||
ox::Error redo() noexcept final;
|
||||
|
||||
void undo() noexcept final;
|
||||
ox::Error undo() noexcept final;
|
||||
|
||||
[[nodiscard]]
|
||||
int commandId() const noexcept final;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include <ox/std/point.hpp>
|
||||
#include <keel/media.hpp>
|
||||
#include <studio/studio.hpp>
|
||||
|
||||
#include "tilesheeteditor-imgui.hpp"
|
||||
|
||||
@@ -14,6 +15,16 @@ namespace nostalgia::core {
|
||||
|
||||
namespace ig = studio::ig;
|
||||
|
||||
struct TileSheetEditorConfig {
|
||||
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.studio.TileSheetEditorConfig";
|
||||
static constexpr auto TypeVersion = 1;
|
||||
TileSheet::SubSheetIdx activeSubsheet{};
|
||||
};
|
||||
|
||||
OX_MODEL_BEGIN(TileSheetEditorConfig)
|
||||
OX_MODEL_FIELD_RENAME(activeSubsheet, active_subsheet)
|
||||
OX_MODEL_END()
|
||||
|
||||
static ox::Vector<uint32_t> normalizePixelSizes(
|
||||
ox::Vector<uint8_t> const&inPixels,
|
||||
int const bpp) noexcept {
|
||||
@@ -65,7 +76,7 @@ static ox::Error toPngFile(
|
||||
c = color32(color(pal, page, c)) | static_cast<Color32>(0XFF << 24);
|
||||
}
|
||||
constexpr auto fmt = LCT_RGBA;
|
||||
return OxError(static_cast<ox::ErrorCode>(
|
||||
return ox::Error(static_cast<ox::ErrorCode>(
|
||||
lodepng_encode_file(
|
||||
path.c_str(),
|
||||
reinterpret_cast<uint8_t const*>(pixels.data()),
|
||||
@@ -75,18 +86,23 @@ static ox::Error toPngFile(
|
||||
8)));
|
||||
}
|
||||
|
||||
TileSheetEditorImGui::TileSheetEditorImGui(studio::StudioContext &sctx, ox::CRStringView path):
|
||||
Editor(path),
|
||||
TileSheetEditorImGui::TileSheetEditorImGui(studio::StudioContext &sctx, ox::StringParam path):
|
||||
Editor(std::move(path)),
|
||||
m_sctx(sctx),
|
||||
m_tctx(m_sctx.tctx),
|
||||
m_view(m_sctx, path, *undoStack()),
|
||||
m_view(m_sctx, itemPath(), *undoStack()),
|
||||
m_model(m_view.model()) {
|
||||
std::ignore = setPaletteSelection();
|
||||
// connect signal/slots
|
||||
undoStack()->changeTriggered.connect(this, &TileSheetEditorImGui::markUnsavedChanges);
|
||||
m_subsheetEditor.inputSubmitted.connect(this, &TileSheetEditorImGui::updateActiveSubsheet);
|
||||
m_exportMenu.inputSubmitted.connect(this, &TileSheetEditorImGui::exportSubhseetToPng);
|
||||
m_model.paletteChanged.connect(this, &TileSheetEditorImGui::setPaletteSelection);
|
||||
// load config
|
||||
auto const&config = studio::readConfig<TileSheetEditorConfig>(
|
||||
keelCtx(m_sctx), itemPath());
|
||||
if (config.ok()) {
|
||||
m_model.setActiveSubsheet(validateSubSheetIdx(m_model.img(), config.value.activeSubsheet));
|
||||
}
|
||||
}
|
||||
|
||||
void TileSheetEditorImGui::exportFile() {
|
||||
@@ -105,6 +121,10 @@ void TileSheetEditorImGui::paste() {
|
||||
m_model.paste();
|
||||
}
|
||||
|
||||
bool TileSheetEditorImGui::acceptsClipboardPayload() const noexcept {
|
||||
return m_model.acceptsClipboardPayload();
|
||||
}
|
||||
|
||||
void TileSheetEditorImGui::keyStateChanged(turbine::Key key, bool down) {
|
||||
if (!down) {
|
||||
return;
|
||||
@@ -113,23 +133,23 @@ void TileSheetEditorImGui::keyStateChanged(turbine::Key key, bool down) {
|
||||
m_subsheetEditor.close();
|
||||
m_exportMenu.close();
|
||||
}
|
||||
auto const popupOpen = m_subsheetEditor.isOpen() && m_exportMenu.isOpen();
|
||||
auto const popupOpen = m_subsheetEditor.isOpen() || m_exportMenu.isOpen();
|
||||
auto const pal = m_model.pal();
|
||||
if (!popupOpen) {
|
||||
auto const colorCnt = pal.pages[m_model.palettePage()].size();
|
||||
auto const colorCnt = core::colorCnt(pal, m_model.palettePage());
|
||||
if (key == turbine::Key::Alpha_D) {
|
||||
m_tool = Tool::Draw;
|
||||
m_tool = TileSheetTool::Draw;
|
||||
setCopyEnabled(false);
|
||||
setCutEnabled(false);
|
||||
setPasteEnabled(false);
|
||||
m_model.clearSelection();
|
||||
} else if (key == turbine::Key::Alpha_S) {
|
||||
m_tool = Tool::Select;
|
||||
m_tool = TileSheetTool::Select;
|
||||
setCopyEnabled(true);
|
||||
setCutEnabled(true);
|
||||
setPasteEnabled(true);
|
||||
} else if (key == turbine::Key::Alpha_F) {
|
||||
m_tool = Tool::Fill;
|
||||
m_tool = TileSheetTool::Fill;
|
||||
setCopyEnabled(false);
|
||||
setCutEnabled(false);
|
||||
setPasteEnabled(false);
|
||||
@@ -137,20 +157,25 @@ void TileSheetEditorImGui::keyStateChanged(turbine::Key key, bool down) {
|
||||
} else if (key >= turbine::Key::Num_1 && key <= turbine::Key::Num_9) {
|
||||
if (turbine::buttonDown(m_tctx, turbine::Key::Mod_Alt)) {
|
||||
auto const idx = ox::min<std::size_t>(
|
||||
static_cast<uint32_t>(key - turbine::Key::Num_1), m_model.pal().pages.size() - 1);
|
||||
static_cast<uint32_t>(key - turbine::Key::Num_1),
|
||||
m_model.pal().pages.size() - 1);
|
||||
m_model.setPalettePage(idx);
|
||||
} else if (key <= turbine::Key::Num_0 + colorCnt) {
|
||||
auto const idx = ox::min<std::size_t>(static_cast<uint32_t>(key - turbine::Key::Num_1), colorCnt - 1);
|
||||
} else if (key <= turbine::Key::Num_9) {
|
||||
auto const idx = ox::min<std::size_t>(
|
||||
static_cast<uint32_t>(key - turbine::Key::Num_1),
|
||||
colorCnt - 1);
|
||||
m_view.setPalIdx(idx);
|
||||
}
|
||||
} else if (key == turbine::Key::Num_0) {
|
||||
if (turbine::buttonDown(m_tctx, turbine::Key::Mod_Alt)) {
|
||||
auto const idx = ox::min<std::size_t>(
|
||||
static_cast<uint32_t>(key - turbine::Key::Num_1 + 9), m_model.pal().pages.size() - 1);
|
||||
static_cast<uint32_t>(key - turbine::Key::Num_1 + 9),
|
||||
m_model.pal().pages.size() - 1);
|
||||
m_model.setPalettePage(idx);
|
||||
} else if (colorCnt >= 10) {
|
||||
} else {
|
||||
auto const idx = ox::min<std::size_t>(
|
||||
static_cast<uint32_t>(key - turbine::Key::Num_1 + 9), colorCnt - 1);
|
||||
static_cast<uint32_t>(key - turbine::Key::Num_1 + 9),
|
||||
colorCnt - 1);
|
||||
m_view.setPalIdx(idx);
|
||||
}
|
||||
}
|
||||
@@ -158,53 +183,64 @@ void TileSheetEditorImGui::keyStateChanged(turbine::Key key, bool down) {
|
||||
}
|
||||
|
||||
void TileSheetEditorImGui::draw(studio::StudioContext&) noexcept {
|
||||
auto const popupOpen = m_subsheetEditor.isOpen() || m_exportMenu.isOpen();
|
||||
if (!popupOpen && m_tool == TileSheetTool::Select) {
|
||||
if (ImGui::IsKeyDown(ImGuiKey_ModCtrl)) {
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_A)) {
|
||||
auto const&img = m_model.activeSubSheet();
|
||||
m_model.setSelection({{}, {img.columns * TileWidth, img.rows * TileHeight}});
|
||||
} else if (ImGui::IsKeyPressed(ImGuiKey_G)) {
|
||||
m_model.clearSelection();
|
||||
}
|
||||
}
|
||||
}
|
||||
auto const paneSize = ImGui::GetContentRegionAvail();
|
||||
auto const tileSheetParentSize = ImVec2(paneSize.x - m_palViewWidth, paneSize.y);
|
||||
auto const fbSize = ox::Vec2(tileSheetParentSize.x - 16, tileSheetParentSize.y - 16);
|
||||
auto const tileSheetParentSize = ImVec2{paneSize.x - s_palViewWidth, paneSize.y};
|
||||
auto const fbSize = ox::Vec2{tileSheetParentSize.x - 16, tileSheetParentSize.y - 16};
|
||||
ImGui::BeginChild("TileSheetView", tileSheetParentSize, true);
|
||||
{
|
||||
drawTileSheet(fbSize);
|
||||
}
|
||||
ImGui::EndChild();
|
||||
ImGui::SameLine();
|
||||
ImGui::BeginChild("Controls", ImVec2(m_palViewWidth - 8, paneSize.y), true);
|
||||
ImGui::BeginChild("Controls", {s_palViewWidth - 8, paneSize.y}, true);
|
||||
{
|
||||
auto const controlsSize = ImGui::GetContentRegionAvail();
|
||||
ImGui::BeginChild("ToolBox", ImVec2(m_palViewWidth - 24, 30), true);
|
||||
ImGui::BeginChild("ToolBox", {s_palViewWidth - 24, 30}, true);
|
||||
{
|
||||
auto const btnSz = ImVec2(45, 14);
|
||||
if (ImGui::Selectable("Select", m_tool == Tool::Select, 0, btnSz)) {
|
||||
m_tool = Tool::Select;
|
||||
auto const btnSz = ImVec2{45, 14};
|
||||
if (ImGui::Selectable("Select", m_tool == TileSheetTool::Select, 0, btnSz)) {
|
||||
m_tool = TileSheetTool::Select;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Selectable("Draw", m_tool == Tool::Draw, 0, btnSz)) {
|
||||
m_tool = Tool::Draw;
|
||||
if (ImGui::Selectable("Draw", m_tool == TileSheetTool::Draw, 0, btnSz)) {
|
||||
m_tool = TileSheetTool::Draw;
|
||||
m_model.clearSelection();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Selectable("Fill", m_tool == Tool::Fill, 0, btnSz)) {
|
||||
m_tool = Tool::Fill;
|
||||
if (ImGui::Selectable("Fill", m_tool == TileSheetTool::Fill, 0, btnSz)) {
|
||||
m_tool = TileSheetTool::Fill;
|
||||
m_model.clearSelection();
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
auto const ySize = controlsSize.y - 38;
|
||||
// draw palette/color picker
|
||||
ImGui::BeginChild("Palette", ImVec2(m_palViewWidth - 24, ySize / 2.f), true);
|
||||
ImGui::BeginChild("Palette", {s_palViewWidth - 24, ySize / 2.f}, true);
|
||||
{
|
||||
drawPaletteSelector();
|
||||
drawPaletteMenu();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
ImGui::BeginChild("SubSheets", ImVec2(m_palViewWidth - 24, ySize / 2.f), true);
|
||||
ImGui::BeginChild("SubSheets", {s_palViewWidth - 24, ySize / 2.f}, true);
|
||||
{
|
||||
static constexpr auto btnHeight = ig::BtnSz.y;
|
||||
auto const btnSize = ImVec2(btnHeight, btnHeight);
|
||||
auto const btnSize = ImVec2{btnHeight, btnHeight};
|
||||
if (ig::PushButton("+", btnSize)) {
|
||||
auto insertOnIdx = m_model.activeSubSheetIdx();
|
||||
auto const&parent = m_model.activeSubSheet();
|
||||
m_model.addSubsheet(insertOnIdx);
|
||||
insertOnIdx.emplace_back(parent.subsheets.size() - 1);
|
||||
m_model.setActiveSubsheet(insertOnIdx);
|
||||
setActiveSubsheet(insertOnIdx);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ig::PushButton("-", btnSize)) {
|
||||
@@ -237,7 +273,7 @@ void TileSheetEditorImGui::draw(studio::StudioContext&) noexcept {
|
||||
}
|
||||
ImGui::EndChild();
|
||||
m_subsheetEditor.draw(m_tctx);
|
||||
m_exportMenu.draw(m_sctx);
|
||||
m_exportMenu.draw(m_tctx);
|
||||
}
|
||||
|
||||
void TileSheetEditorImGui::drawSubsheetSelector(
|
||||
@@ -257,7 +293,7 @@ void TileSheetEditorImGui::drawSubsheetSelector(
|
||||
auto const open = ImGui::TreeNodeEx(lbl.c_str(), flags);
|
||||
ImGui::SameLine();
|
||||
if (ImGui::IsItemClicked()) {
|
||||
m_model.setActiveSubsheet(path);
|
||||
setActiveSubsheet(path);
|
||||
}
|
||||
if (ImGui::IsMouseDoubleClicked(0) && ImGui::IsItemHovered()) {
|
||||
showSubsheetEditor();
|
||||
@@ -279,9 +315,8 @@ void TileSheetEditorImGui::drawSubsheetSelector(
|
||||
for (auto i = 0ul; auto &child : subsheet.subsheets) {
|
||||
path.push_back(i);
|
||||
ImGui::PushID(static_cast<int>(i));
|
||||
ImGui::Indent(-indentReduce);
|
||||
ig::IndentStackItem const indentStackItem{-indentReduce};
|
||||
drawSubsheetSelector(child, path);
|
||||
ImGui::Indent(indentReduce);
|
||||
ImGui::PopID();
|
||||
path.pop_back();
|
||||
++i;
|
||||
@@ -310,8 +345,8 @@ void TileSheetEditorImGui::showSubsheetEditor() noexcept {
|
||||
}
|
||||
}
|
||||
|
||||
ox::Error TileSheetEditorImGui::exportSubhseetToPng(int scale) noexcept {
|
||||
oxRequire(path, studio::saveFile({{"PNG", "png"}}));
|
||||
ox::Error TileSheetEditorImGui::exportSubhseetToPng(int const scale) const noexcept {
|
||||
OX_REQUIRE(path, studio::saveFile({{"PNG", "png"}}));
|
||||
// subsheet to png
|
||||
auto const&img = m_model.img();
|
||||
auto const&s = m_model.activeSubSheet();
|
||||
@@ -335,7 +370,7 @@ ox::Error TileSheetEditorImGui::exportSubhseetToPng(int scale) noexcept {
|
||||
|
||||
void TileSheetEditorImGui::drawTileSheet(ox::Vec2 const&fbSize) noexcept {
|
||||
auto const winPos = ImGui::GetWindowPos();
|
||||
auto const fbSizei = ox::Size(static_cast<int>(fbSize.x), static_cast<int>(fbSize.y));
|
||||
auto const fbSizei = ox::Size{static_cast<int>(fbSize.x), static_cast<int>(fbSize.y)};
|
||||
if (m_framebuffer.width != fbSizei.width || m_framebuffer.height != fbSizei.height) {
|
||||
glutils::resizeInitFrameBuffer(m_framebuffer, fbSizei.width, fbSizei.height);
|
||||
m_view.resizeView(fbSize);
|
||||
@@ -351,41 +386,39 @@ void TileSheetEditorImGui::drawTileSheet(ox::Vec2 const&fbSize) noexcept {
|
||||
ImGui::Image(
|
||||
ig::toImTextureID(m_framebuffer.color.id),
|
||||
static_cast<ImVec2>(fbSize),
|
||||
ImVec2(0, 1),
|
||||
ImVec2(1, 0));
|
||||
{0, 1},
|
||||
{1, 0});
|
||||
// handle input, this must come after drawing
|
||||
auto const&io = ImGui::GetIO();
|
||||
auto const mousePos = ox::Vec2(io.MousePos);
|
||||
auto const mousePos = ox::Vec2{ImGui::GetMousePos()};
|
||||
if (ImGui::IsItemHovered()) {
|
||||
auto const wheel = io.MouseWheel;
|
||||
auto const wheelh = io.MouseWheelH;
|
||||
if (wheel != 0) {
|
||||
const auto zoomMod = ox::defines::OS == ox::OS::Darwin ?
|
||||
io.KeySuper : turbine::buttonDown(m_tctx, turbine::Key::Mod_Ctrl);
|
||||
m_view.scrollV(fbSize, wheel, zoomMod);
|
||||
m_view.scrollV(fbSize, wheel, ImGui::IsKeyDown(ImGuiKey_ModCtrl));
|
||||
}
|
||||
if (wheelh != 0) {
|
||||
m_view.scrollH(fbSize, wheelh);
|
||||
}
|
||||
if (io.MouseDown[0] && m_prevMouseDownPos != mousePos) {
|
||||
if (ImGui::IsMouseDown(0) && m_prevMouseDownPos != mousePos) {
|
||||
m_prevMouseDownPos = mousePos;
|
||||
switch (m_tool) {
|
||||
case Tool::Draw:
|
||||
case TileSheetTool::Draw:
|
||||
m_view.clickDraw(fbSize, clickPos(winPos, mousePos));
|
||||
break;
|
||||
case Tool::Fill:
|
||||
case TileSheetTool::Fill:
|
||||
m_view.clickFill(fbSize, clickPos(winPos, mousePos));
|
||||
break;
|
||||
case Tool::Select:
|
||||
case TileSheetTool::Select:
|
||||
m_view.clickSelect(fbSize, clickPos(winPos, mousePos));
|
||||
break;
|
||||
case Tool::None:
|
||||
case TileSheetTool::None:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ImGui::BeginPopupContextItem("TileMenu", ImGuiPopupFlags_MouseButtonRight)) {
|
||||
auto const popupPos = ox::Vec2(ImGui::GetWindowPos());
|
||||
auto const popupPos = ox::Vec2{ImGui::GetWindowPos()};
|
||||
if (ImGui::MenuItem("Insert Tile")) {
|
||||
m_view.insertTile(fbSize, clickPos(winPos, popupPos));
|
||||
}
|
||||
@@ -396,11 +429,11 @@ void TileSheetEditorImGui::drawTileSheet(ox::Vec2 const&fbSize) noexcept {
|
||||
}
|
||||
if (io.MouseReleased[0]) {
|
||||
m_prevMouseDownPos = {-1, -1};
|
||||
m_view.releaseMouseButton();
|
||||
m_view.releaseMouseButton(m_tool);
|
||||
}
|
||||
}
|
||||
|
||||
void TileSheetEditorImGui::drawPaletteSelector() noexcept {
|
||||
void TileSheetEditorImGui::drawPaletteMenu() noexcept {
|
||||
auto const&files = m_sctx.project->fileList(core::FileExt_npal);
|
||||
auto const comboWidthSub = 62;
|
||||
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - comboWidthSub);
|
||||
@@ -409,13 +442,15 @@ void TileSheetEditorImGui::drawPaletteSelector() noexcept {
|
||||
}
|
||||
auto const pages = m_model.pal().pages.size();
|
||||
if (pages > 1) {
|
||||
ImGui::Indent(20);
|
||||
auto numStr = ox::itoa(m_model.palettePage() + 1);
|
||||
ig::IndentStackItem const indentStackItem{20};
|
||||
using Str = ox::IString<55>;
|
||||
auto numStr = ox::sfmt<Str>(
|
||||
"{} - {}", m_model.palettePage() + 1, m_model.pal().pages[m_model.palettePage()].name);
|
||||
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - comboWidthSub);
|
||||
if (ImGui::BeginCombo("Page", numStr.c_str(), 0)) {
|
||||
for (auto n = 0u; n < pages; ++n) {
|
||||
auto const selected = (m_model.palettePage() == n);
|
||||
numStr = ox::itoa(n + 1);
|
||||
numStr = ox::sfmt<Str>("{} - {}", n + 1, m_model.pal().pages[n].name);
|
||||
if (ImGui::Selectable(numStr.c_str(), selected) && m_model.palettePage() != n) {
|
||||
m_model.setPalettePage(n);
|
||||
}
|
||||
@@ -425,34 +460,41 @@ void TileSheetEditorImGui::drawPaletteSelector() noexcept {
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
ImGui::Indent(-20);
|
||||
}
|
||||
// header
|
||||
if (ImGui::BeginTable("PaletteTable", 3, ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchProp)) {
|
||||
ImGui::TableSetupColumn("No.", 0, 0.45f);
|
||||
if (ImGui::BeginTable(
|
||||
"PaletteTable", 4, ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchProp)) {
|
||||
ImGui::TableSetupColumn("Idx", 0, 0.6f);
|
||||
ImGui::TableSetupColumn("", 0, 0.22f);
|
||||
ImGui::TableSetupColumn("Name", 0, 3);
|
||||
ImGui::TableSetupColumn("Color16", 0, 3);
|
||||
ImGui::TableHeadersRow();
|
||||
{
|
||||
auto const&pal = m_model.pal();
|
||||
for (auto i = 0u; auto c: pal.pages[m_model.palettePage()]) {
|
||||
ImGui::PushID(static_cast<int>(i));
|
||||
// Column: color idx
|
||||
ImGui::TableNextColumn();
|
||||
auto const label = ox::itoa(i + 1);
|
||||
auto const rowSelected = i == m_view.palIdx();
|
||||
if (ImGui::Selectable(label.c_str(), rowSelected, ImGuiSelectableFlags_SpanAllColumns)) {
|
||||
m_view.setPalIdx(i);
|
||||
if (pal.pages.size() > m_model.palettePage()) {
|
||||
for (auto i = 0u; auto const&c: pal.pages[m_model.palettePage()].colors) {
|
||||
ImGui::TableNextRow();
|
||||
ImGui::PushID(static_cast<int>(i));
|
||||
// Column: color idx
|
||||
ImGui::TableNextColumn();
|
||||
auto const label = ox::itoa(i + 1);
|
||||
auto const rowSelected = i == m_view.palIdx();
|
||||
if (ImGui::Selectable(
|
||||
label.c_str(), rowSelected, ImGuiSelectableFlags_SpanAllColumns)) {
|
||||
m_view.setPalIdx(i);
|
||||
}
|
||||
// Column: color RGB
|
||||
ImGui::TableNextColumn();
|
||||
auto ic = ImGui::GetColorU32(ImVec4(redf(c), greenf(c), bluef(c), 1));
|
||||
ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, ic);
|
||||
ImGui::TableNextColumn();
|
||||
auto const&name = i < pal.colorNames.size() ? pal.colorNames[i].c_str() : "";
|
||||
ImGui::Text("%s", name);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("(%02d, %02d, %02d)", red16(c), green16(c), blue16(c));
|
||||
ImGui::PopID();
|
||||
++i;
|
||||
}
|
||||
// Column: color RGB
|
||||
ImGui::TableNextColumn();
|
||||
auto ic = ImGui::GetColorU32(ImVec4(redf(c), greenf(c), bluef(c), 1));
|
||||
ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, ic);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("(%02d, %02d, %02d)", red16(c), green16(c), blue16(c));
|
||||
ImGui::TableNextRow();
|
||||
ImGui::PopID();
|
||||
++i;
|
||||
}
|
||||
}
|
||||
ImGui::EndTable();
|
||||
@@ -476,12 +518,23 @@ ox::Error TileSheetEditorImGui::setPaletteSelection() noexcept {
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Error TileSheetEditorImGui::markUnsavedChanges(studio::UndoCommand const*) noexcept {
|
||||
setUnsavedChanges(true);
|
||||
return {};
|
||||
void TileSheetEditorImGui::setActiveSubsheet(TileSheet::SubSheetIdx path) noexcept {
|
||||
m_model.setActiveSubsheet(path);
|
||||
studio::editConfig<TileSheetEditorConfig>(keelCtx(m_sctx), itemPath(),
|
||||
[&path](TileSheetEditorConfig &config) {
|
||||
config.activeSubsheet = std::move(path);
|
||||
});
|
||||
}
|
||||
|
||||
void TileSheetEditorImGui::SubSheetEditor::draw(turbine::Context &sctx) noexcept {
|
||||
|
||||
void TileSheetEditorImGui::SubSheetEditor::show(ox::StringViewCR name, int const cols, int const rows) noexcept {
|
||||
m_show = true;
|
||||
m_name = name;
|
||||
m_cols = cols;
|
||||
m_rows = rows;
|
||||
}
|
||||
|
||||
void TileSheetEditorImGui::SubSheetEditor::draw(turbine::Context &tctx) noexcept {
|
||||
constexpr auto popupName = "Edit Subsheet";
|
||||
if (!m_show) {
|
||||
return;
|
||||
@@ -489,8 +542,8 @@ void TileSheetEditorImGui::SubSheetEditor::draw(turbine::Context &sctx) noexcept
|
||||
auto const modSize = m_cols > 0;
|
||||
auto constexpr popupWidth = 235.f;
|
||||
auto const popupHeight = modSize ? 130.f : 85.f;
|
||||
auto const popupSz = ImVec2(popupWidth, popupHeight);
|
||||
if (ig::BeginPopup(sctx, popupName, m_show, popupSz)) {
|
||||
auto const popupSz = ImVec2{popupWidth, popupHeight};
|
||||
if (ig::BeginPopup(tctx, popupName, m_show, popupSz)) {
|
||||
ImGui::InputText("Name", m_name.data(), m_name.cap());
|
||||
if (modSize) {
|
||||
ImGui::InputInt("Columns", &m_cols);
|
||||
@@ -507,15 +560,21 @@ void TileSheetEditorImGui::SubSheetEditor::close() noexcept {
|
||||
m_show = false;
|
||||
}
|
||||
|
||||
void TileSheetEditorImGui::ExportMenu::draw(studio::StudioContext &sctx) noexcept {
|
||||
|
||||
void TileSheetEditorImGui::ExportMenu::show() noexcept {
|
||||
m_show = true;
|
||||
m_scale = 5;
|
||||
}
|
||||
|
||||
void TileSheetEditorImGui::ExportMenu::draw(turbine::Context &tctx) noexcept {
|
||||
constexpr auto popupName = "Export Tile Sheet";
|
||||
if (!m_show) {
|
||||
return;
|
||||
}
|
||||
constexpr auto popupWidth = 235.f;
|
||||
constexpr auto popupHeight = 85.f;
|
||||
constexpr auto popupSz = ImVec2(popupWidth, popupHeight);
|
||||
if (ig::BeginPopup(sctx.tctx, popupName, m_show, popupSz)) {
|
||||
constexpr auto popupSz = ImVec2{popupWidth, popupHeight};
|
||||
if (ig::BeginPopup(tctx, popupName, m_show, popupSz)) {
|
||||
ImGui::InputInt("Scale", &m_scale);
|
||||
m_scale = ox::clamp(m_scale, 1, 50);
|
||||
if (ig::PopupControlsOkCancel(popupWidth, m_show) == ig::PopupResponse::OK) {
|
||||
|
||||
@@ -16,13 +16,6 @@
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
enum class Tool {
|
||||
None,
|
||||
Draw,
|
||||
Fill,
|
||||
Select,
|
||||
};
|
||||
|
||||
class TileSheetEditorImGui: public studio::Editor {
|
||||
|
||||
private:
|
||||
@@ -33,17 +26,12 @@ class TileSheetEditorImGui: public studio::Editor {
|
||||
int m_rows = 0;
|
||||
bool m_show = false;
|
||||
public:
|
||||
ox::Signal<ox::Error(ox::StringView const&name, int cols, int rows)> inputSubmitted;
|
||||
constexpr void show(ox::StringView const&name, int cols, int rows) noexcept {
|
||||
m_show = true;
|
||||
m_name = name;
|
||||
m_cols = cols;
|
||||
m_rows = rows;
|
||||
}
|
||||
ox::Signal<ox::Error(ox::StringViewCR name, int cols, int rows)> inputSubmitted;
|
||||
void show(ox::StringViewCR name, int cols, int rows) noexcept;
|
||||
void draw(turbine::Context &sctx) noexcept;
|
||||
void close() noexcept;
|
||||
[[nodiscard]]
|
||||
inline bool isOpen() const noexcept { return m_show; }
|
||||
constexpr bool isOpen() const noexcept { return m_show; }
|
||||
};
|
||||
class ExportMenu {
|
||||
private:
|
||||
@@ -51,15 +39,13 @@ class TileSheetEditorImGui: public studio::Editor {
|
||||
bool m_show = false;
|
||||
public:
|
||||
ox::Signal<ox::Error(int scale)> inputSubmitted;
|
||||
constexpr void show() noexcept {
|
||||
m_show = true;
|
||||
m_scale = 5;
|
||||
}
|
||||
void draw(studio::StudioContext &sctx) noexcept;
|
||||
void show() noexcept;
|
||||
void draw(turbine::Context &sctx) noexcept;
|
||||
void close() noexcept;
|
||||
[[nodiscard]]
|
||||
inline bool isOpen() const noexcept { return m_show; }
|
||||
constexpr bool isOpen() const noexcept { return m_show; }
|
||||
};
|
||||
static constexpr float s_palViewWidth = 300;
|
||||
std::size_t m_selectedPaletteIdx = 0;
|
||||
studio::StudioContext &m_sctx;
|
||||
turbine::Context &m_tctx;
|
||||
@@ -69,12 +55,11 @@ class TileSheetEditorImGui: public studio::Editor {
|
||||
glutils::FrameBuffer m_framebuffer;
|
||||
TileSheetEditorView m_view;
|
||||
TileSheetEditorModel &m_model;
|
||||
float m_palViewWidth = 300;
|
||||
ox::Vec2 m_prevMouseDownPos;
|
||||
Tool m_tool = Tool::Draw;
|
||||
TileSheetTool m_tool = TileSheetTool::Draw;
|
||||
|
||||
public:
|
||||
TileSheetEditorImGui(studio::StudioContext &sctx, ox::CRStringView path);
|
||||
TileSheetEditorImGui(studio::StudioContext &sctx, ox::StringParam path);
|
||||
|
||||
~TileSheetEditorImGui() override = default;
|
||||
|
||||
@@ -86,6 +71,9 @@ class TileSheetEditorImGui: public studio::Editor {
|
||||
|
||||
void paste() override;
|
||||
|
||||
[[nodiscard]]
|
||||
bool acceptsClipboardPayload() const noexcept override;
|
||||
|
||||
void keyStateChanged(turbine::Key key, bool down) override;
|
||||
|
||||
void draw(studio::StudioContext&) noexcept override;
|
||||
@@ -101,11 +89,11 @@ class TileSheetEditorImGui: public studio::Editor {
|
||||
private:
|
||||
void showSubsheetEditor() noexcept;
|
||||
|
||||
ox::Error exportSubhseetToPng(int scale) noexcept;
|
||||
ox::Error exportSubhseetToPng(int scale) const noexcept;
|
||||
|
||||
void drawTileSheet(ox::Vec2 const&fbSize) noexcept;
|
||||
|
||||
void drawPaletteSelector() noexcept;
|
||||
void drawPaletteMenu() noexcept;
|
||||
|
||||
ox::Error updateActiveSubsheet(ox::StringView const&name, int cols, int rows) noexcept;
|
||||
|
||||
@@ -113,7 +101,7 @@ class TileSheetEditorImGui: public studio::Editor {
|
||||
|
||||
// slots
|
||||
private:
|
||||
ox::Error markUnsavedChanges(studio::UndoCommand const*) noexcept;
|
||||
void setActiveSubsheet(TileSheet::SubSheetIdx path) noexcept;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -27,7 +27,8 @@
|
||||
namespace nostalgia::core {
|
||||
|
||||
Palette const TileSheetEditorModel::s_defaultPalette = {
|
||||
.pages = {ox::Vector<Color16>(128)},
|
||||
.colorNames = {ox::Vector<ox::String>{{}}},
|
||||
.pages = {{"Page 1", ox::Vector<Color16>(128)}},
|
||||
};
|
||||
|
||||
// delete pixels of all non-leaf nodes
|
||||
@@ -55,55 +56,63 @@ TileSheetEditorModel::TileSheetEditorModel(studio::StudioContext &sctx, ox::Stri
|
||||
}
|
||||
|
||||
void TileSheetEditorModel::cut() {
|
||||
if (!m_selection) {
|
||||
return;
|
||||
}
|
||||
TileSheetClipboard blankCb;
|
||||
auto cb = ox::make_unique<TileSheetClipboard>();
|
||||
const auto &s = activeSubSheet();
|
||||
for (int y = m_selectionBounds.y; y <= m_selectionBounds.y2(); ++y) {
|
||||
for (int x = m_selectionBounds.x; x <= m_selectionBounds.x2(); ++x) {
|
||||
auto pt = ox::Point(x, y);
|
||||
const auto idx = core::idx(s, pt);
|
||||
const auto c = getPixel(s, m_img.bpp, idx);
|
||||
pt.x -= m_selectionBounds.x;
|
||||
pt.y -= m_selectionBounds.y;
|
||||
cb->addPixel(pt, c);
|
||||
blankCb.addPixel(pt, 0);
|
||||
}
|
||||
}
|
||||
const auto pt1 = m_selectionOrigin == ox::Point(-1, -1) ? ox::Point(0, 0) : m_selectionOrigin;
|
||||
const auto pt2 = ox::Point(s.columns * TileWidth, s.rows * TileHeight);
|
||||
auto const&s = activeSubSheet();
|
||||
iterateSelectionRows(*m_selection, [&](int x, int y) {
|
||||
auto pt = ox::Point{x, y};
|
||||
auto const idx = core::idx(s, pt);
|
||||
auto const c = getPixel(s, m_img.bpp, idx);
|
||||
pt -= m_selection->a;
|
||||
cb->addPixel(pt, c);
|
||||
blankCb.addPixel(pt, 0);
|
||||
});
|
||||
auto const pt1 = m_selection->a;
|
||||
auto const pt2 = ox::Point{s.columns * TileWidth, s.rows * TileHeight};
|
||||
turbine::setClipboardObject(m_tctx, std::move(cb));
|
||||
pushCommand(ox::make<CutPasteCommand>(CommandId::Cut, m_img, m_activeSubsSheetIdx, pt1, pt2, blankCb));
|
||||
}
|
||||
|
||||
void TileSheetEditorModel::copy() {
|
||||
auto cb = ox::make_unique<TileSheetClipboard>();
|
||||
for (int y = m_selectionBounds.y; y <= m_selectionBounds.y2(); ++y) {
|
||||
for (int x = m_selectionBounds.x; x <= m_selectionBounds.x2(); ++x) {
|
||||
auto pt = ox::Point(x, y);
|
||||
const auto &s = activeSubSheet();
|
||||
const auto idx = core::idx(s, pt);
|
||||
const auto c = getPixel(s, m_img.bpp, idx);
|
||||
pt.x -= m_selectionBounds.x;
|
||||
pt.y -= m_selectionBounds.y;
|
||||
cb->addPixel(pt, c);
|
||||
}
|
||||
if (!m_selection) {
|
||||
return;
|
||||
}
|
||||
auto cb = ox::make_unique<TileSheetClipboard>();
|
||||
iterateSelectionRows(*m_selection, [&](int x, int y) {
|
||||
auto pt = ox::Point{x, y};
|
||||
const auto&s = activeSubSheet();
|
||||
const auto idx = core::idx(s, pt);
|
||||
const auto c = getPixel(s, m_img.bpp, idx);
|
||||
pt -= m_selection->a;
|
||||
cb->addPixel(pt, c);
|
||||
});
|
||||
turbine::setClipboardObject(m_tctx, std::move(cb));
|
||||
}
|
||||
|
||||
void TileSheetEditorModel::paste() {
|
||||
if (!m_selection) {
|
||||
return;
|
||||
}
|
||||
auto [cb, err] = turbine::getClipboardObject<TileSheetClipboard>(m_tctx);
|
||||
if (err) {
|
||||
oxLogError(err);
|
||||
oxErrf("Could not read clipboard: {}", toStr(err));
|
||||
return;
|
||||
}
|
||||
const auto &s = activeSubSheet();
|
||||
const auto pt1 = m_selectionOrigin == ox::Point(-1, -1) ? ox::Point(0, 0) : m_selectionOrigin;
|
||||
const auto pt2 = ox::Point(s.columns * TileWidth, s.rows * TileHeight);
|
||||
auto const&s = activeSubSheet();
|
||||
auto const pt1 = m_selection->a;
|
||||
auto const pt2 = ox::Point{s.columns * TileWidth, s.rows * TileHeight};
|
||||
pushCommand(ox::make<CutPasteCommand>(CommandId::Paste, m_img, m_activeSubsSheetIdx, pt1, pt2, *cb));
|
||||
}
|
||||
|
||||
bool TileSheetEditorModel::acceptsClipboardPayload() const noexcept {
|
||||
auto const cb = getClipboardObject<TileSheetClipboard>(m_tctx);
|
||||
return cb.ok();
|
||||
}
|
||||
|
||||
ox::StringView TileSheetEditorModel::palPath() const noexcept {
|
||||
auto [path, err] = m_img.defaultPalette.getPath();
|
||||
if (err) {
|
||||
@@ -111,7 +120,7 @@ ox::StringView TileSheetEditorModel::palPath() const noexcept {
|
||||
}
|
||||
constexpr ox::StringView uuidPrefix = "uuid://";
|
||||
if (ox::beginsWith(path, uuidPrefix)) {
|
||||
auto uuid = ox::StringView(path.data() + uuidPrefix.bytes(), path.bytes() - uuidPrefix.bytes());
|
||||
auto uuid = ox::StringView(&path[uuidPrefix.bytes()], path.bytes() - uuidPrefix.bytes());
|
||||
auto out = keelCtx(m_tctx).uuidToPath.at(uuid);
|
||||
if (out.error) {
|
||||
return {};
|
||||
@@ -123,7 +132,7 @@ ox::StringView TileSheetEditorModel::palPath() const noexcept {
|
||||
}
|
||||
|
||||
ox::Error TileSheetEditorModel::setPalette(ox::StringView path) noexcept {
|
||||
oxRequire(uuid, keelCtx(m_tctx).pathToUuid.at(path));
|
||||
OX_REQUIRE(uuid, keelCtx(m_tctx).pathToUuid.at(path));
|
||||
pushCommand(ox::make<PaletteChangeCommand>(activeSubSheetIdx(), m_img, uuid->toString()));
|
||||
return {};
|
||||
}
|
||||
@@ -181,16 +190,16 @@ void TileSheetEditorModel::setActiveSubsheet(TileSheet::SubSheetIdx const&idx) n
|
||||
}
|
||||
|
||||
void TileSheetEditorModel::fill(ox::Point const&pt, int palIdx) noexcept {
|
||||
const auto &s = getSubSheet(m_img, m_activeSubsSheetIdx);
|
||||
auto const&activeSubSheet = getSubSheet(m_img, m_activeSubsSheetIdx);
|
||||
// build idx list
|
||||
ox::Array<bool, PixelsPerTile> updateMap = {};
|
||||
const auto oldColor = getPixel(s, m_img.bpp, pt);
|
||||
if (pt.x >= s.columns * TileWidth || pt.y >= s.rows * TileHeight) {
|
||||
if (pt.x >= activeSubSheet.columns * TileWidth || pt.y >= activeSubSheet.rows * TileHeight) {
|
||||
return;
|
||||
}
|
||||
getFillPixels(updateMap.data(), pt, oldColor);
|
||||
ox::Array<bool, PixelsPerTile> updateMap = {};
|
||||
auto const oldColor = getPixel(activeSubSheet, m_img.bpp, pt);
|
||||
getFillPixels(updateMap, pt, oldColor);
|
||||
ox::Vector<std::size_t> idxList;
|
||||
auto i = core::idx(s, pt) / PixelsPerTile * PixelsPerTile;
|
||||
auto i = core::idx(activeSubSheet, pt) / PixelsPerTile * PixelsPerTile;
|
||||
for (auto u : updateMap) {
|
||||
if (u) {
|
||||
idxList.emplace_back(i);
|
||||
@@ -200,36 +209,37 @@ void TileSheetEditorModel::fill(ox::Point const&pt, int palIdx) noexcept {
|
||||
// do updates to sheet
|
||||
if (m_ongoingDrawCommand) {
|
||||
m_updated = m_updated || m_ongoingDrawCommand->append(idxList);
|
||||
} else if (getPixel(s, m_img.bpp, pt) != palIdx) {
|
||||
} else if (getPixel(activeSubSheet, m_img.bpp, pt) != palIdx) {
|
||||
pushCommand(ox::make<DrawCommand>(m_img, m_activeSubsSheetIdx, idxList, palIdx));
|
||||
}
|
||||
}
|
||||
|
||||
void TileSheetEditorModel::setSelection(studio::Selection const&sel) noexcept {
|
||||
m_selection.emplace(sel);
|
||||
m_updated = true;
|
||||
}
|
||||
|
||||
void TileSheetEditorModel::select(ox::Point const&pt) noexcept {
|
||||
if (!m_selectionOngoing) {
|
||||
m_selectionOrigin = pt;
|
||||
m_selectionOngoing = true;
|
||||
m_selectionBounds = {pt, pt};
|
||||
m_updated = true;
|
||||
} else if (m_selectionBounds.pt2() != pt) {
|
||||
m_selectionBounds = {m_selectionOrigin, pt};
|
||||
m_updated = true;
|
||||
if (m_selTracker.updateCursorPoint(pt)) {
|
||||
setSelection(m_selTracker.selection());
|
||||
}
|
||||
}
|
||||
|
||||
void TileSheetEditorModel::completeSelection() noexcept {
|
||||
m_selectionOngoing = false;
|
||||
auto &s = activeSubSheet();
|
||||
auto pt = m_selectionBounds.pt2();
|
||||
pt.x = ox::min(s.columns * TileWidth, pt.x);
|
||||
pt.y = ox::min(s.rows * TileHeight, pt.y);
|
||||
m_selectionBounds.setPt2(pt);
|
||||
if (m_selTracker.selectionOngoing()) {
|
||||
m_selTracker.finishSelection();
|
||||
m_selection.emplace(m_selTracker.selection());
|
||||
auto&pt = m_selection->b;
|
||||
auto&s = activeSubSheet();
|
||||
pt.x = ox::min(s.columns * TileWidth - 1, pt.x);
|
||||
pt.y = ox::min(s.rows * TileHeight - 1, pt.y);
|
||||
}
|
||||
}
|
||||
|
||||
void TileSheetEditorModel::clearSelection() noexcept {
|
||||
m_updated = true;
|
||||
m_selectionOrigin = {-1, -1};
|
||||
m_selectionBounds = {{-1, -1}, {-1, -1}};
|
||||
m_selTracker.reset();
|
||||
m_selection.reset();
|
||||
}
|
||||
|
||||
bool TileSheetEditorModel::updated() const noexcept {
|
||||
@@ -240,7 +250,7 @@ ox::Error TileSheetEditorModel::markUpdatedCmdId(studio::UndoCommand const*cmd)
|
||||
m_updated = true;
|
||||
const auto cmdId = cmd->commandId();
|
||||
if (static_cast<CommandId>(cmdId) == CommandId::PaletteChange) {
|
||||
oxReturnError(readObj<Palette>(keelCtx(m_tctx), m_img.defaultPalette).moveTo(m_pal));
|
||||
OX_RETURN_ERROR(readObj<Palette>(keelCtx(m_tctx), m_img.defaultPalette).moveTo(m_pal));
|
||||
m_palettePage = ox::min<size_t>(m_pal->pages.size(), 0);
|
||||
paletteChanged.emit();
|
||||
}
|
||||
@@ -266,12 +276,12 @@ ox::Error TileSheetEditorModel::saveFile() noexcept {
|
||||
}
|
||||
|
||||
bool TileSheetEditorModel::pixelSelected(std::size_t idx) const noexcept {
|
||||
const auto &s = activeSubSheet();
|
||||
const auto pt = idxToPt(static_cast<int>(idx), s.columns);
|
||||
return m_selectionBounds.contains(pt);
|
||||
auto const&s = activeSubSheet();
|
||||
auto const pt = idxToPt(static_cast<int>(idx), s.columns);
|
||||
return m_selection && m_selection->contains(pt);
|
||||
}
|
||||
|
||||
void TileSheetEditorModel::getFillPixels(bool *pixels, ox::Point const&pt, int oldColor) const noexcept {
|
||||
void TileSheetEditorModel::getFillPixels(ox::Span<bool> pixels, ox::Point const&pt, int oldColor) const noexcept {
|
||||
const auto &activeSubSheet = this->activeSubSheet();
|
||||
const auto tileIdx = [activeSubSheet](const ox::Point &pt) noexcept {
|
||||
return ptToIdx(pt, activeSubSheet.columns) / PixelsPerTile;
|
||||
@@ -305,7 +315,7 @@ void TileSheetEditorModel::getFillPixels(bool *pixels, ox::Point const&pt, int o
|
||||
}
|
||||
|
||||
void TileSheetEditorModel::pushCommand(studio::UndoCommand *cmd) noexcept {
|
||||
m_undoStack.push(ox::UPtr<studio::UndoCommand>(cmd));
|
||||
std::ignore = m_undoStack.push(ox::UPtr<studio::UndoCommand>(cmd));
|
||||
m_ongoingDrawCommand = dynamic_cast<DrawCommand*>(cmd);
|
||||
m_updated = true;
|
||||
}
|
||||
|
||||
@@ -33,10 +33,9 @@ class TileSheetEditorModel: public ox::SignalHandler {
|
||||
size_t m_palettePage{};
|
||||
studio::UndoStack &m_undoStack;
|
||||
class DrawCommand *m_ongoingDrawCommand = nullptr;
|
||||
studio::SelectionTracker m_selTracker;
|
||||
ox::Optional<studio::Selection> m_selection;
|
||||
bool m_updated = false;
|
||||
bool m_selectionOngoing = false;
|
||||
ox::Point m_selectionOrigin = {-1, -1};
|
||||
ox::Bounds m_selectionBounds = {{-1, -1}, {-1, -1}};
|
||||
|
||||
public:
|
||||
TileSheetEditorModel(studio::StudioContext &sctx, ox::StringView path, studio::UndoStack &undoStack);
|
||||
@@ -49,6 +48,9 @@ class TileSheetEditorModel: public ox::SignalHandler {
|
||||
|
||||
void paste();
|
||||
|
||||
[[nodiscard]]
|
||||
bool acceptsClipboardPayload() const noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr TileSheet const&img() const noexcept;
|
||||
|
||||
@@ -101,6 +103,8 @@ class TileSheetEditorModel: public ox::SignalHandler {
|
||||
|
||||
void fill(ox::Point const&pt, int palIdx) noexcept;
|
||||
|
||||
void setSelection(studio::Selection const&sel) noexcept;
|
||||
|
||||
void select(ox::Point const&pt) noexcept;
|
||||
|
||||
void completeSelection() noexcept;
|
||||
@@ -124,7 +128,7 @@ class TileSheetEditorModel: public ox::SignalHandler {
|
||||
bool pixelSelected(std::size_t idx) const noexcept;
|
||||
|
||||
private:
|
||||
void getFillPixels(bool *pixels, ox::Point const&pt, int oldColor) const noexcept;
|
||||
void getFillPixels(ox::Span<bool> pixels, ox::Point const&pt, int oldColor) const noexcept;
|
||||
|
||||
void pushCommand(studio::UndoCommand *cmd) noexcept;
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@ TileSheetEditorView::TileSheetEditorView(studio::StudioContext &sctx, ox::String
|
||||
m_pixelsDrawer(m_model) {
|
||||
glBindVertexArray(0);
|
||||
// build shaders
|
||||
oxThrowError(m_pixelsDrawer.buildShader());
|
||||
oxThrowError(m_pixelGridDrawer.buildShader());
|
||||
OX_THROW_ERROR(m_pixelsDrawer.buildShader());
|
||||
OX_THROW_ERROR(m_pixelGridDrawer.buildShader());
|
||||
m_model.activeSubsheetChanged.connect(this, &TileSheetEditorView::setActiveSubsheet);
|
||||
}
|
||||
|
||||
@@ -84,9 +84,18 @@ void TileSheetEditorView::clickFill(ox::Vec2 const&paneSize, ox::Vec2 const&clic
|
||||
m_model.fill(pt, static_cast<int>(m_palIdx));
|
||||
}
|
||||
|
||||
void TileSheetEditorView::releaseMouseButton() noexcept {
|
||||
m_model.endDrawCommand();
|
||||
m_model.completeSelection();
|
||||
void TileSheetEditorView::releaseMouseButton(TileSheetTool tool) noexcept {
|
||||
switch (tool) {
|
||||
case TileSheetTool::Draw:
|
||||
case TileSheetTool::Fill:
|
||||
m_model.endDrawCommand();
|
||||
break;
|
||||
case TileSheetTool::Select:
|
||||
m_model.completeSelection();
|
||||
break;
|
||||
case TileSheetTool::None:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void TileSheetEditorView::resizeView(ox::Vec2 const&sz) noexcept {
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
|
||||
namespace nostalgia::core {
|
||||
|
||||
enum class TileSheetTool: int {
|
||||
enum class TileSheetTool {
|
||||
None,
|
||||
Select,
|
||||
Draw,
|
||||
Fill,
|
||||
@@ -33,6 +34,8 @@ constexpr auto toString(TileSheetTool t) noexcept {
|
||||
return "Draw";
|
||||
case TileSheetTool::Fill:
|
||||
return "Fill";
|
||||
case TileSheetTool::None:
|
||||
return "None";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
@@ -66,7 +69,7 @@ class TileSheetEditorView: public ox::SignalHandler {
|
||||
|
||||
void clickFill(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept;
|
||||
|
||||
void releaseMouseButton() noexcept;
|
||||
void releaseMouseButton(TileSheetTool tool) noexcept;
|
||||
|
||||
void scrollV(ox::Vec2 const&paneSz, float wheel, bool zoomMod) noexcept;
|
||||
|
||||
|
||||
@@ -139,10 +139,7 @@ void TileSheetPixels::setBufferObjects(ox::Vec2 const&paneSize) noexcept {
|
||||
return;
|
||||
}
|
||||
if (m_model.pixelSelected(i)) {
|
||||
auto const r = red16(color) / 2;
|
||||
auto const g = (green16(color) + 20) / 2;
|
||||
auto const b = (blue16(color) + 31) / 2;
|
||||
color = color16(r, g, b);
|
||||
color = applySelectionColor(color);
|
||||
}
|
||||
setPixelBufferObject(paneSize, static_cast<unsigned>(i * VertexVboRows), fx, fy, color, vbo, ebo);
|
||||
});
|
||||
|
||||
@@ -15,11 +15,11 @@ std::size_t idx(TileSheet::SubSheet const&ss, ox::Point const&pt) noexcept {
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
static TileSheet::SubSheet const*getSubsheet(TileSheet::SubSheet const&ss, SubSheetId const id) noexcept {
|
||||
static TileSheet::SubSheet const *getSubsheet(TileSheet::SubSheet const&ss, SubSheetId const id) noexcept {
|
||||
if (ss.id == id) {
|
||||
return &ss;
|
||||
}
|
||||
for (auto const&child : ss.subsheets) {
|
||||
for (auto const&child: ss.subsheets) {
|
||||
if (auto out = getSubsheet(child, id)) {
|
||||
return out;
|
||||
}
|
||||
@@ -34,7 +34,7 @@ static size_t getTileCnt(TileSheet::SubSheet const&ss, int const bpp) noexcept {
|
||||
return ss.pixels.size() / bytesPerTile;
|
||||
} else {
|
||||
size_t out{};
|
||||
for (auto const&child : ss.subsheets) {
|
||||
for (auto const&child: ss.subsheets) {
|
||||
out += getTileCnt(child, bpp);
|
||||
}
|
||||
return out;
|
||||
@@ -45,16 +45,16 @@ size_t getTileCnt(TileSheet const&ts) noexcept {
|
||||
return getTileCnt(ts.subsheet, ts.bpp);
|
||||
}
|
||||
|
||||
TileSheet::SubSheet const*getSubsheet(TileSheet const&ts, SubSheetId const id) noexcept {
|
||||
TileSheet::SubSheet const *getSubsheet(TileSheet const&ts, SubSheetId const id) noexcept {
|
||||
return getSubsheet(ts.subsheet, id);
|
||||
}
|
||||
|
||||
static ox::Optional<size_t> getPixelIdx(
|
||||
TileSheet::SubSheet const&ss,
|
||||
SubSheetId const id,
|
||||
size_t idx,
|
||||
size_t &idx,
|
||||
int8_t const bpp) noexcept {
|
||||
for (auto const&child : ss.subsheets) {
|
||||
for (auto const&child: ss.subsheets) {
|
||||
if (child.id == id) {
|
||||
return ox::Optional<size_t>(ox::in_place, idx);
|
||||
}
|
||||
@@ -63,11 +63,12 @@ static ox::Optional<size_t> getPixelIdx(
|
||||
}
|
||||
idx += pixelCnt(child, bpp);
|
||||
}
|
||||
return {};
|
||||
return ox::Optional<size_t>{};
|
||||
}
|
||||
|
||||
size_t getTileIdx(TileSheet const&ts, SubSheetId const id) noexcept {
|
||||
auto const out = getPixelIdx(ts.subsheet, id, 0, ts.bpp);
|
||||
size_t idx{};
|
||||
auto const out = getPixelIdx(ts.subsheet, id, idx, ts.bpp);
|
||||
return out.or_value(0) / PixelsPerTile;
|
||||
}
|
||||
|
||||
@@ -106,8 +107,8 @@ uint8_t getPixel(TileSheet::SubSheet const&ss, int8_t pBpp, ox::Point const&pt)
|
||||
return getPixel(ss, pBpp, idx);
|
||||
}
|
||||
|
||||
void setPixel(TileSheet::SubSheet &ss, int8_t pBpp, uint64_t idx, uint8_t palIdx) noexcept {
|
||||
auto &pixel = ss.pixels[static_cast<std::size_t>(idx / 2)];
|
||||
static void setPixel(ox::Vector<uint8_t> &pixels, int8_t pBpp, uint64_t idx, uint8_t palIdx) noexcept {
|
||||
auto &pixel = pixels[static_cast<std::size_t>(idx / 2)];
|
||||
if (pBpp == 4) {
|
||||
if (idx & 1) {
|
||||
pixel = static_cast<uint8_t>((pixel & 0b0000'1111) | (palIdx << 4));
|
||||
@@ -119,22 +120,39 @@ void setPixel(TileSheet::SubSheet &ss, int8_t pBpp, uint64_t idx, uint8_t palIdx
|
||||
}
|
||||
}
|
||||
|
||||
void setPixel(TileSheet::SubSheet &ss, int8_t pBpp, uint64_t idx, uint8_t palIdx) noexcept {
|
||||
setPixel(ss.pixels, pBpp, idx, palIdx);
|
||||
}
|
||||
|
||||
static void setPixel(ox::Vector<uint8_t> &pixels, int columns, int8_t pBpp, ox::Point const&pt, uint8_t palIdx) noexcept {
|
||||
const auto idx = ptToIdx(pt, columns);
|
||||
setPixel(pixels, pBpp, idx, palIdx);
|
||||
}
|
||||
|
||||
void setPixel(TileSheet::SubSheet &ss, int8_t pBpp, ox::Point const&pt, uint8_t palIdx) noexcept {
|
||||
const auto idx = ptToIdx(pt, ss.columns);
|
||||
setPixel(ss, pBpp, idx, palIdx);
|
||||
}
|
||||
|
||||
ox::Error setPixelCount(TileSheet::SubSheet &ss, int8_t pBpp, std::size_t cnt) noexcept {
|
||||
static ox::Error setPixelCount(ox::Vector<uint8_t> &pixels, int8_t pBpp, std::size_t cnt) noexcept {
|
||||
size_t sz{};
|
||||
switch (pBpp) {
|
||||
case 4:
|
||||
ss.pixels.resize(cnt / 2);
|
||||
return OxError(0);
|
||||
sz = cnt / 2;
|
||||
break;
|
||||
case 8:
|
||||
ss.pixels.resize(cnt);
|
||||
return OxError(0);
|
||||
sz = cnt;
|
||||
break;
|
||||
default:
|
||||
return OxError(1, "Invalid pBpp used for TileSheet::SubSheet::setPixelCount");
|
||||
return ox::Error(1, "Invalid pBpp used for TileSheet::SubSheet::setPixelCount");
|
||||
}
|
||||
pixels.reserve(sz);
|
||||
pixels.resize(sz);
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Error setPixelCount(TileSheet::SubSheet &ss, int8_t pBpp, std::size_t cnt) noexcept {
|
||||
return setPixelCount(ss.pixels, pBpp, cnt);
|
||||
}
|
||||
|
||||
unsigned pixelCnt(TileSheet::SubSheet const&ss, int8_t pBpp) noexcept {
|
||||
@@ -142,6 +160,23 @@ unsigned pixelCnt(TileSheet::SubSheet const&ss, int8_t pBpp) noexcept {
|
||||
return pBpp == 4 ? pixelsSize * 2 : pixelsSize;
|
||||
}
|
||||
|
||||
ox::Error resizeSubsheet(TileSheet::SubSheet &ss, int8_t pBpp, ox::Size const&sz) noexcept {
|
||||
ox::Vector<uint8_t> out;
|
||||
OX_RETURN_ERROR(setPixelCount(out, pBpp, static_cast<size_t>(sz.width * sz.height) * PixelsPerTile));
|
||||
auto const w = ox::min<int32_t>(ss.columns, sz.width) * TileWidth;
|
||||
auto const h = ox::min<int32_t>(ss.rows, sz.height) * TileHeight;
|
||||
for (auto x = 0; x < w; ++x) {
|
||||
for (auto y = 0; y < h; ++y) {
|
||||
auto const palIdx = getPixel(ss, pBpp, {x, y});
|
||||
setPixel(out, sz.width, pBpp, {x, y}, palIdx);
|
||||
}
|
||||
}
|
||||
ss.columns = sz.width;
|
||||
ss.rows = sz.height;
|
||||
ss.pixels = std::move(out);
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Result<ox::StringView> getNameFor(TileSheet::SubSheet const&ss, SubSheetId pId) noexcept {
|
||||
if (ss.id == pId) {
|
||||
return ox::StringView(ss.name);
|
||||
@@ -152,32 +187,33 @@ ox::Result<ox::StringView> getNameFor(TileSheet::SubSheet const&ss, SubSheetId p
|
||||
return name;
|
||||
}
|
||||
}
|
||||
return OxError(1, "SubSheet not found");
|
||||
return ox::Error(1, "SubSheet not found");
|
||||
}
|
||||
|
||||
|
||||
TileSheet::SubSheetIdx validateSubSheetIdx(
|
||||
TileSheet::SubSheetIdx const&pIdx,
|
||||
TileSheet::SubSheetIdx &&pIdx,
|
||||
std::size_t pIdxIt,
|
||||
TileSheet::SubSheet const&pSubsheet) noexcept {
|
||||
if (pIdxIt == pIdx.size()) {
|
||||
return pIdx;
|
||||
if (pIdxIt >= pIdx.size()) {
|
||||
return std::move(pIdx);
|
||||
}
|
||||
const auto currentIdx = pIdx[pIdxIt];
|
||||
auto ¤tIdx = pIdx[pIdxIt];
|
||||
if (pSubsheet.subsheets.size() <= currentIdx) {
|
||||
auto out = pIdx;
|
||||
if (!pSubsheet.subsheets.empty()) {
|
||||
*out.back().value = pSubsheet.subsheets.size() - 1;
|
||||
if (pSubsheet.subsheets.empty()) {
|
||||
// currentIdx could not be repaired, remove
|
||||
// this and all succeeding idxs and return
|
||||
pIdx.resize(pIdxIt);
|
||||
return std::move(pIdx);
|
||||
} else {
|
||||
out.pop_back();
|
||||
currentIdx = pSubsheet.subsheets.size() - 1;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
return validateSubSheetIdx(pIdx, pIdxIt + 1, pSubsheet.subsheets[pIdx[pIdxIt]]);
|
||||
return validateSubSheetIdx(std::move(pIdx), pIdxIt + 1, pSubsheet.subsheets[currentIdx]);
|
||||
}
|
||||
|
||||
TileSheet::SubSheetIdx validateSubSheetIdx(TileSheet const&ts, TileSheet::SubSheetIdx const&idx) noexcept {
|
||||
return validateSubSheetIdx(idx, 0, ts.subsheet);
|
||||
TileSheet::SubSheetIdx validateSubSheetIdx(TileSheet const&ts, TileSheet::SubSheetIdx idx) noexcept {
|
||||
return validateSubSheetIdx(std::move(idx), 0, ts.subsheet);
|
||||
}
|
||||
|
||||
const TileSheet::SubSheet &getSubSheet(
|
||||
@@ -220,7 +256,7 @@ ox::Error addSubSheet(TileSheet &ts, TileSheet::SubSheetIdx const&idx) noexcept
|
||||
parent.subsheets.emplace_back(++ts.idIt, "Subsheet 0", parent.columns, parent.rows, ts.bpp);
|
||||
parent.subsheets.emplace_back(++ts.idIt, "Subsheet 1", 1, 1, ts.bpp);
|
||||
}
|
||||
return OxError(0);
|
||||
return ox::Error(0);
|
||||
}
|
||||
|
||||
ox::Error rmSubSheet(
|
||||
@@ -244,7 +280,7 @@ uint8_t getPixel4Bpp(
|
||||
TileSheet::SubSheetIdx const&subsheetIdx) noexcept {
|
||||
oxAssert(ts.bpp == 4, "TileSheet::getPixel4Bpp: wrong bpp");
|
||||
auto &s = getSubSheet(ts, subsheetIdx);
|
||||
const auto idx = ptToIdx(pt, s.columns);
|
||||
auto const idx = ptToIdx(pt, s.columns);
|
||||
return getPixel4Bpp(s, idx);
|
||||
}
|
||||
|
||||
@@ -254,10 +290,49 @@ uint8_t getPixel8Bpp(
|
||||
TileSheet::SubSheetIdx const&subsheetIdx) noexcept {
|
||||
oxAssert(ts.bpp == 8, "TileSheet::getPixel8Bpp: wrong bpp");
|
||||
auto &s = getSubSheet(ts, subsheetIdx);
|
||||
const auto idx = ptToIdx(pt, s.columns);
|
||||
auto const idx = ptToIdx(pt, s.columns);
|
||||
return getPixel8Bpp(s, idx);
|
||||
}
|
||||
|
||||
uint8_t getPixel4Bpp(
|
||||
CompactTileSheet const&ts,
|
||||
size_t const idx) noexcept {
|
||||
oxAssert(ts.bpp == 4, "TileSheet::getPixel4Bpp: wrong bpp");
|
||||
if (idx & 1) {
|
||||
return ts.pixels[idx / 2] >> 4;
|
||||
} else {
|
||||
return ts.pixels[idx / 2] & 0b0000'1111;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t getPixel8Bpp(
|
||||
CompactTileSheet const&ts,
|
||||
size_t const idx) noexcept {
|
||||
oxAssert(ts.bpp == 8, "TileSheet::getPixel8Bpp: wrong bpp");
|
||||
return ts.pixels[idx];
|
||||
}
|
||||
|
||||
ox::Pair<uint8_t> get2Pixels4Bpp(
|
||||
CompactTileSheet const&ts,
|
||||
size_t const idx) noexcept {
|
||||
oxAssert(ts.bpp == 4, "TileSheet::getPixel4Bpp: wrong bpp");
|
||||
auto const out = ts.pixels[idx / 2];
|
||||
return {
|
||||
static_cast<uint8_t>(out & 0x0f),
|
||||
static_cast<uint8_t>(out >> 4),
|
||||
};
|
||||
}
|
||||
|
||||
ox::Pair<uint8_t> get2Pixels8Bpp(
|
||||
CompactTileSheet const&ts,
|
||||
size_t const idx) noexcept {
|
||||
oxAssert(ts.bpp == 8, "TileSheet::getPixel8Bpp: wrong bpp");
|
||||
return {
|
||||
static_cast<uint8_t>(ts.pixels[idx]),
|
||||
static_cast<uint8_t>(ts.pixels[idx + 1]),
|
||||
};
|
||||
}
|
||||
|
||||
static ox::Result<SubSheetId> getIdFor(
|
||||
TileSheet::SubSheet const&ss,
|
||||
ox::SpanView<ox::StringView> const&pNamePath,
|
||||
@@ -270,10 +345,10 @@ static ox::Result<SubSheetId> getIdFor(
|
||||
return getIdFor(ss, pNamePath, pIt + 1);
|
||||
}
|
||||
}
|
||||
return OxError(1, "SubSheet not found");
|
||||
return ox::Error(1, "SubSheet not found");
|
||||
}
|
||||
|
||||
ox::Result<SubSheetId> getIdFor(TileSheet const&ts, ox::CRStringView path) noexcept {
|
||||
ox::Result<SubSheetId> getIdFor(TileSheet const&ts, ox::StringViewCR path) noexcept {
|
||||
return getIdFor(ts.subsheet, ox::split<8>(path, '.'));
|
||||
}
|
||||
|
||||
@@ -288,7 +363,7 @@ static ox::Result<unsigned> getTileOffset(
|
||||
unsigned pCurrentTotal = 0) noexcept {
|
||||
// pIt == pNamePath.size() - 1 &&
|
||||
if (ss.name != pNamePath[pIt]) {
|
||||
return OxError(2, "Wrong branch");
|
||||
return ox::Error(2, "Wrong branch");
|
||||
}
|
||||
if (pIt == pNamePath.size() - 1) {
|
||||
return pCurrentTotal;
|
||||
@@ -301,10 +376,10 @@ static ox::Result<unsigned> getTileOffset(
|
||||
}
|
||||
pCurrentTotal += pixelCnt(sub, pBpp) / PixelsPerTile;
|
||||
}
|
||||
return OxError(1, "SubSheet not found");
|
||||
return ox::Error(1, "SubSheet not found");
|
||||
}
|
||||
|
||||
ox::Result<unsigned> getTileOffset(TileSheet const&ts, ox::CRStringView pNamePath) noexcept {
|
||||
ox::Result<unsigned> getTileOffset(TileSheet const&ts, ox::StringViewCR pNamePath) noexcept {
|
||||
return core::getTileOffset(ts.subsheet, ox::split<8>(pNamePath, '.'), ts.bpp);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,17 +16,18 @@ static std::map<ox::StringView, ox::Error(*)()> tests = {
|
||||
"readWriteTileSheet",
|
||||
[]() -> ox::Error {
|
||||
core::TileSheet in;
|
||||
oxRequire(buff, ox::writeMC(in));
|
||||
oxRequire(out, ox::readMC<core::TileSheet>(buff));
|
||||
OX_REQUIRE(buff, ox::writeMC(in));
|
||||
OX_REQUIRE(out, ox::readMC<core::TileSheet>(buff));
|
||||
oxAssert(in.subsheet.name == out.subsheet.name, "subsheet.name serialization broken");
|
||||
return {};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
int main(int argc, const char **args) {
|
||||
int main(int argc, const char **argv) {
|
||||
int retval = -1;
|
||||
if (argc > 0) {
|
||||
auto const args = ox::Span{argv, static_cast<size_t>(argc)};
|
||||
auto const testName = ox::StringView(args[1]);
|
||||
if (tests.find(testName) != tests.end()) {
|
||||
retval = static_cast<int>(tests[testName]());
|
||||
|
||||
@@ -57,12 +57,12 @@ struct TileDoc {
|
||||
|
||||
};
|
||||
|
||||
oxModelBegin(TileDoc)
|
||||
oxModelFieldRename(subsheetId, subsheet_id)
|
||||
oxModelFieldRename(subsheetPath, subsheet_path)
|
||||
oxModelField(type)
|
||||
oxModelFieldRename(layerAttachments, layer_attachments)
|
||||
oxModelEnd()
|
||||
OX_MODEL_BEGIN(TileDoc)
|
||||
OX_MODEL_FIELD_RENAME(subsheetId, subsheet_id)
|
||||
OX_MODEL_FIELD_RENAME(subsheetPath, subsheet_path)
|
||||
OX_MODEL_FIELD(type)
|
||||
OX_MODEL_FIELD_RENAME(layerAttachments, layer_attachments)
|
||||
OX_MODEL_END()
|
||||
|
||||
struct SceneDoc {
|
||||
|
||||
@@ -95,11 +95,11 @@ struct SceneDoc {
|
||||
}
|
||||
};
|
||||
|
||||
oxModelBegin(SceneDoc)
|
||||
oxModelField(tilesheet)
|
||||
oxModelField(palettes)
|
||||
oxModelField(tiles)
|
||||
oxModelEnd()
|
||||
OX_MODEL_BEGIN(SceneDoc)
|
||||
OX_MODEL_FIELD(tilesheet)
|
||||
OX_MODEL_FIELD(palettes)
|
||||
OX_MODEL_FIELD(tiles)
|
||||
OX_MODEL_END()
|
||||
|
||||
|
||||
constexpr void setTopEdge(uint8_t &layerAttachments, unsigned val) noexcept {
|
||||
@@ -215,14 +215,14 @@ struct SceneStatic {
|
||||
|
||||
};
|
||||
|
||||
oxModelBegin(SceneStatic)
|
||||
oxModelField(tilesheet)
|
||||
oxModelField(palettes)
|
||||
oxModelField(columns)
|
||||
oxModelField(rows)
|
||||
oxModelField(tileMapIdx)
|
||||
oxModelField(tileType)
|
||||
oxModelField(layerAttachments)
|
||||
oxModelEnd()
|
||||
OX_MODEL_BEGIN(SceneStatic)
|
||||
OX_MODEL_FIELD(tilesheet)
|
||||
OX_MODEL_FIELD(palettes)
|
||||
OX_MODEL_FIELD(columns)
|
||||
OX_MODEL_FIELD(rows)
|
||||
OX_MODEL_FIELD(tileMapIdx)
|
||||
OX_MODEL_FIELD(tileType)
|
||||
OX_MODEL_FIELD(layerAttachments)
|
||||
OX_MODEL_END()
|
||||
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ ox::Error SceneDocToSceneStaticConverter::convert(
|
||||
keel::Context &ctx,
|
||||
SceneDoc &src,
|
||||
SceneStatic &dst) const noexcept {
|
||||
oxRequire(ts, keel::readObj<core::TileSheet>(ctx, src.tilesheet));
|
||||
OX_REQUIRE(ts, keel::readObj<core::TileSheet>(ctx, src.tilesheet));
|
||||
const auto layerCnt = src.tiles.size();
|
||||
dst.setLayerCnt(layerCnt);
|
||||
dst.tilesheet = ox::FileAddress(src.tilesheet);
|
||||
@@ -53,8 +53,8 @@ ox::Error SceneDocToSceneStaticConverter::convert(
|
||||
for (const auto &srcTile : row) {
|
||||
auto dstTile = dstLayer.tile(tileIdx);
|
||||
dstTile.tileType = srcTile.type;
|
||||
oxRequire(path, srcTile.getSubsheetPath(*ts));
|
||||
oxRequire(mapIdx, getTileOffset(*ts, path));
|
||||
OX_REQUIRE(path, srcTile.getSubsheetPath(*ts));
|
||||
OX_REQUIRE(mapIdx, getTileOffset(*ts, path));
|
||||
dstTile.tileMapIdx = static_cast<uint16_t>(mapIdx);
|
||||
setLayerAttachments(layerIdx, srcTile, dstTile);
|
||||
++tileIdx;
|
||||
|
||||
@@ -14,11 +14,11 @@ Scene::Scene(SceneStatic const&sceneStatic) noexcept:
|
||||
|
||||
ox::Error Scene::setupDisplay(core::Context &ctx) const noexcept {
|
||||
if (m_sceneStatic.palettes.empty()) {
|
||||
return OxError(1, "Scene has no palettes");
|
||||
return ox::Error(1, "Scene has no palettes");
|
||||
}
|
||||
auto const&palette = m_sceneStatic.palettes[0];
|
||||
oxReturnError(core::loadBgTileSheet(ctx, 0, m_sceneStatic.tilesheet));
|
||||
oxReturnError(core::loadBgPalette(ctx, 0, palette));
|
||||
OX_RETURN_ERROR(core::loadBgTileSheet(ctx, 0, m_sceneStatic.tilesheet));
|
||||
OX_RETURN_ERROR(core::loadBgPalette(ctx, 0, palette));
|
||||
// disable all backgrounds
|
||||
core::setBgStatus(ctx, 0);
|
||||
for (auto layerNo = 0u; auto const&layer : m_sceneStatic.tileMapIdx) {
|
||||
|
||||
@@ -15,7 +15,6 @@ SceneEditorImGui::SceneEditorImGui(studio::StudioContext &ctx, ox::StringView pa
|
||||
m_ctx(ctx.tctx),
|
||||
m_editor(m_ctx, path),
|
||||
m_view(m_ctx, m_editor.scene()) {
|
||||
setRequiresConstantRefresh(false);
|
||||
}
|
||||
|
||||
void SceneEditorImGui::draw(studio::StudioContext&) noexcept {
|
||||
@@ -49,8 +48,7 @@ void SceneEditorImGui::onActivated() noexcept {
|
||||
|
||||
ox::Error SceneEditorImGui::saveItem() noexcept {
|
||||
const auto sctx = applicationData<studio::StudioContext>(m_ctx);
|
||||
oxReturnError(sctx->project->writeObj(itemPath(), m_editor.scene()));
|
||||
oxReturnError(keelCtx(m_ctx).assetManager.setAsset(itemPath(), m_editor.scene()));
|
||||
OX_RETURN_ERROR(sctx->project->writeObj(itemPath(), m_editor.scene()));
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
namespace nostalgia::scene {
|
||||
|
||||
SceneEditor::SceneEditor(turbine::Context &ctx, ox::CRStringView path):
|
||||
SceneEditor::SceneEditor(turbine::Context &ctx, ox::StringViewCR path):
|
||||
m_ctx(ctx),
|
||||
m_scene(*keel::readObj<SceneStatic>(keelCtx(m_ctx), path).unwrapThrow()) {
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ class SceneEditor {
|
||||
SceneStatic m_scene;
|
||||
|
||||
public:
|
||||
SceneEditor(turbine::Context &ctx, ox::CRStringView path);
|
||||
SceneEditor(turbine::Context &ctx, ox::StringViewCR path);
|
||||
|
||||
[[nodiscard]]
|
||||
SceneStatic const&scene() const noexcept {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
add_executable(
|
||||
nostalgia WIN32
|
||||
app.cpp
|
||||
main.cpp
|
||||
)
|
||||
|
||||
# enable LTO
|
||||
@@ -23,6 +22,8 @@ endif()
|
||||
target_link_libraries(
|
||||
nostalgia
|
||||
NostalgiaKeelModules
|
||||
NostalgiaProfile
|
||||
OlympicApplib
|
||||
OxLogConn
|
||||
)
|
||||
|
||||
|
||||
@@ -65,14 +65,14 @@ static void testKeyEventHandler(turbine::Context &tctx, turbine::Key key, bool d
|
||||
|
||||
[[maybe_unused]]
|
||||
static ox::Error runTest(turbine::Context &tctx) {
|
||||
constexpr ox::FileAddress TileSheetAddr = ox::StringLiteral("/TileSheets/Charset.ng");
|
||||
constexpr ox::FileAddress PaletteAddr = ox::StringLiteral("/Palettes/Chester.npal");
|
||||
oxRequireM(cctx, core::init(tctx));
|
||||
constexpr ox::StringView TileSheetAddr{"/TileSheets/Charset.ng"};
|
||||
constexpr ox::StringView PaletteAddr{"/Palettes/Chester.npal"};
|
||||
OX_REQUIRE_M(cctx, core::init(tctx));
|
||||
turbine::setApplicationData(tctx, cctx.get());
|
||||
oxRequire(tsStat, turbine::rom(tctx)->stat(PaletteAddr));
|
||||
oxReturnError(core::loadSpriteTileSheet(*cctx, TileSheetAddr));
|
||||
oxReturnError(core::loadSpritePalette(*cctx, PaletteAddr));
|
||||
oxReturnError(core::initConsole(*cctx));
|
||||
OX_REQUIRE(tsStat, turbine::rom(tctx)->stat(PaletteAddr));
|
||||
OX_RETURN_ERROR(core::loadSpriteTileSheet(*cctx, TileSheetAddr));
|
||||
OX_RETURN_ERROR(core::loadSpritePalette(*cctx, PaletteAddr));
|
||||
OX_RETURN_ERROR(core::initConsole(*cctx));
|
||||
core::puts(*cctx, 10, 9, "DOPENESS!!!");
|
||||
turbine::setUpdateHandler(tctx, testUpdateHandler);
|
||||
turbine::setKeyEventHandler(tctx, testKeyEventHandler);
|
||||
@@ -84,25 +84,25 @@ static ox::Error runTest(turbine::Context &tctx) {
|
||||
static ox::Error runTileSheetSetTest(turbine::Context &tctx) {
|
||||
// this should make the screen display 'ABCDB', with the A being upside down
|
||||
// and the first B being backwards
|
||||
constexpr ox::FileAddress PaletteAddr = ox::StringLiteral("/Palettes/Charset.npal");
|
||||
oxRequireM(cctx, core::init(tctx));
|
||||
constexpr ox::StringView PaletteAddr{"/Palettes/Charset.npal"};
|
||||
OX_REQUIRE_M(cctx, core::init(tctx));
|
||||
turbine::setApplicationData(tctx, cctx.get());
|
||||
oxRequire(tsStat, turbine::rom(tctx)->stat(PaletteAddr));
|
||||
OX_REQUIRE(tsStat, turbine::rom(tctx)->stat(PaletteAddr));
|
||||
core::TileSheetSet const set{
|
||||
.bpp = 4,
|
||||
.entries = {
|
||||
{ .tilesheet = ox::StringLiteral("/TileSheets/Chester.ng"), .sections{{.begin = 0, .tiles = 1}} },
|
||||
{ .tilesheet = ox::StringLiteral("/TileSheets/AB.ng"), .sections{{.begin = 0, .tiles = 2}} },
|
||||
{ .tilesheet = ox::StringLiteral("/TileSheets/CD.ng"), .sections{{.begin = 0, .tiles = 2}} },
|
||||
{ .tilesheet = ox::StringLiteral("/TileSheets/AB.ng"), .sections{{.begin = 1, .tiles = 1}} },
|
||||
{ .tilesheet = ox::StringLiteral{"/TileSheets/Chester.ng"}, .sections{{.begin = 0, .tiles = 1}} },
|
||||
{ .tilesheet = ox::StringLiteral{"/TileSheets/AB.ng"}, .sections{{.begin = 0, .tiles = 2}} },
|
||||
{ .tilesheet = ox::StringLiteral{"/TileSheets/CD.ng"}, .sections{{.begin = 0, .tiles = 2}} },
|
||||
{ .tilesheet = ox::StringLiteral{"/TileSheets/AB.ng"}, .sections{{.begin = 1, .tiles = 1}} },
|
||||
},
|
||||
};
|
||||
constexpr auto bgPalBank = 1;
|
||||
oxReturnError(core::loadBgTileSheet(*cctx, 0, set));
|
||||
oxReturnError(core::loadSpriteTileSheet(*cctx, set));
|
||||
oxReturnError(core::loadBgPalette(*cctx, bgPalBank, PaletteAddr));
|
||||
oxReturnError(core::loadBgPalette(*cctx, 0, ox::StringLiteral("/Palettes/Chester.npal")));
|
||||
oxReturnError(core::loadSpritePalette(*cctx, PaletteAddr));
|
||||
OX_RETURN_ERROR(core::loadBgTileSheet(*cctx, 0, set));
|
||||
OX_RETURN_ERROR(core::loadSpriteTileSheet(*cctx, set));
|
||||
OX_RETURN_ERROR(core::loadBgPalette(*cctx, bgPalBank, PaletteAddr));
|
||||
OX_RETURN_ERROR(core::loadBgPalette(*cctx, 0, PaletteAddr));
|
||||
OX_RETURN_ERROR(core::loadSpritePalette(*cctx, PaletteAddr));
|
||||
core::setBgStatus(*cctx, 0, true);
|
||||
core::setBgTile(*cctx, 0, 10, 9, { .tileIdx = 1, .palBank = bgPalBank, .flipX = 0, .flipY = 1 });
|
||||
core::setBgTile(*cctx, 0, 11, 9, { .tileIdx = 2, .palBank = bgPalBank, .flipX = 1, .flipY = 0 });
|
||||
@@ -147,17 +147,26 @@ static void sceneKeyEventHandler(turbine::Context &tctx, turbine::Key key, bool
|
||||
|
||||
[[maybe_unused]]
|
||||
static ox::Error runScene(turbine::Context &tctx) {
|
||||
constexpr ox::FileAddress SceneAddr = ox::StringLiteral("/Scenes/Chester.nscn");
|
||||
oxRequireM(cctx, core::init(tctx));
|
||||
oxRequire(scn, keel::readObj<scene::SceneStatic>(keelCtx(tctx), SceneAddr));
|
||||
constexpr ox::StringView SceneAddr{"/Scenes/Chester.nscn"};
|
||||
OX_REQUIRE_M(cctx, core::init(tctx));
|
||||
OX_REQUIRE(scn, keel::readObj<scene::SceneStatic>(keelCtx(tctx), SceneAddr));
|
||||
turbine::setUpdateHandler(tctx, sceneUpdateHandler);
|
||||
turbine::setKeyEventHandler(tctx, sceneKeyEventHandler);
|
||||
scene::Scene const scene(*scn);
|
||||
oxReturnError(scene.setupDisplay(*cctx));
|
||||
OX_RETURN_ERROR(scene.setupDisplay(*cctx));
|
||||
return turbine::run(tctx);
|
||||
}
|
||||
|
||||
ox::Error run(ox::UniquePtr<ox::FileSystem> &&fs) noexcept {
|
||||
oxRequireM(tctx, turbine::init(std::move(fs), "Nostalgia"));
|
||||
ox::Error run(
|
||||
[[maybe_unused]] ox::StringView project,
|
||||
[[maybe_unused]] ox::StringView appName,
|
||||
[[maybe_unused]] ox::StringView projectDataDir,
|
||||
ox::SpanView<char const*> args) noexcept {
|
||||
if (args.size() < 2) {
|
||||
return ox::Error(1, "Please provide path to project directory or OxFS file.");
|
||||
}
|
||||
auto const path = args[1];
|
||||
OX_REQUIRE_M(fs, keel::loadRomFs(path));
|
||||
OX_REQUIRE_M(tctx, turbine::init(std::move(fs), project));
|
||||
return runTileSheetSetTest(*tctx);
|
||||
}
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <ox/logconn/def.hpp>
|
||||
#include <ox/logconn/logconn.hpp>
|
||||
|
||||
#include <keel/media.hpp>
|
||||
|
||||
#include "../modules/keelmodules.hpp"
|
||||
|
||||
#include "app.hpp"
|
||||
|
||||
static ox::Error run(int argc, const char **argv) noexcept {
|
||||
#ifndef OX_BARE_METAL
|
||||
// GBA doesn't need the modules and calling this doubles the size of the
|
||||
// binary.
|
||||
nostalgia::registerKeelModules();
|
||||
#endif
|
||||
if (argc < 2) {
|
||||
return OxError(1, "Please provide path to project directory or OxFS file.");
|
||||
}
|
||||
const auto path = argv[1];
|
||||
oxRequireM(fs, keel::loadRomFs(path));
|
||||
return run(std::move(fs));
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
int WinMain() {
|
||||
auto const argc = __argc;
|
||||
auto const argv = const_cast<const char**>(__argv);
|
||||
#else
|
||||
int main(int argc, const char **argv) {
|
||||
#endif
|
||||
OX_INIT_DEBUG_LOGGER(loggerConn, "Nostalgia Player")
|
||||
ox::Error err;
|
||||
err = run(argc, argv);
|
||||
oxAssert(err, "Something went wrong...");
|
||||
if (err) {
|
||||
oxErrf("Failure: {}\n", toStr(err));
|
||||
}
|
||||
return static_cast<int>(err);
|
||||
}
|
||||
@@ -1,3 +1,8 @@
|
||||
if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang")
|
||||
# enable warnings
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wunsafe-buffer-usage")
|
||||
endif()
|
||||
|
||||
if(BUILDCORE_TARGET STREQUAL "gba")
|
||||
project(Olympic ASM CXX)
|
||||
else()
|
||||
|
||||
@@ -17,6 +17,10 @@
|
||||
#define OLYMPIC_APP_VERSION "dev build"
|
||||
#endif
|
||||
|
||||
#ifndef OLYMPIC_GUI_APP
|
||||
#define OLYMPIC_GUI_APP 0
|
||||
#endif
|
||||
|
||||
#ifndef OLYMPIC_PROJECT_NAMESPACE
|
||||
#define OLYMPIC_PROJECT_NAMESPACE project
|
||||
#endif
|
||||
@@ -34,24 +38,21 @@
|
||||
#endif
|
||||
|
||||
namespace olympic {
|
||||
|
||||
ox::String appVersion = ox::String(OLYMPIC_APP_VERSION);
|
||||
}
|
||||
|
||||
ox::Error run(
|
||||
ox::StringView project,
|
||||
ox::StringView appName,
|
||||
ox::StringView projectDataDir,
|
||||
int argc,
|
||||
char const**argv) noexcept;
|
||||
|
||||
}
|
||||
ox::SpanView<ox::CString> argv) noexcept;
|
||||
|
||||
namespace OLYMPIC_PROJECT_NAMESPACE {
|
||||
void registerKeelModules() noexcept;
|
||||
void registerStudioModules() noexcept;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
#if defined(_WIN32) && OLYMPIC_GUI_APP
|
||||
int WinMain() {
|
||||
auto const argc = __argc;
|
||||
auto const argv = const_cast<const char**>(__argv);
|
||||
@@ -65,7 +66,11 @@ int main(int argc, char const**argv) {
|
||||
#if OLYMPIC_LOAD_STUDIO_MODULES
|
||||
OLYMPIC_PROJECT_NAMESPACE::registerStudioModules();
|
||||
#endif
|
||||
auto const err = olympic::run(OLYMPIC_PROJECT_NAME, OLYMPIC_APP_NAME, OLYMPIC_PROJECT_DATADIR, argc, argv);
|
||||
auto const err = run(
|
||||
OLYMPIC_PROJECT_NAME,
|
||||
OLYMPIC_APP_NAME,
|
||||
OLYMPIC_PROJECT_DATADIR,
|
||||
{argv, static_cast<size_t>(argc)});
|
||||
oxAssert(err, "Something went wrong...");
|
||||
if (err) {
|
||||
oxErrf("Failure: {}\n", toStr(err));
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include <ox/claw/claw.hpp>
|
||||
#include <ox/fs/fs.hpp>
|
||||
|
||||
#include "validation.hpp"
|
||||
|
||||
namespace keel {
|
||||
|
||||
constexpr auto K1HdrSz = 40;
|
||||
@@ -14,8 +16,8 @@ constexpr auto K1HdrSz = 40;
|
||||
ox::Result<ox::UUID> readUuidHeader(ox::BufferView buff) noexcept;
|
||||
|
||||
ox::Error writeUuidHeader(ox::Writer_c auto &writer, ox::UUID const&uuid) noexcept {
|
||||
oxReturnError(write(writer, "K1;"));
|
||||
oxReturnError(uuid.toString(writer));
|
||||
OX_RETURN_ERROR(write(writer, "K1;"));
|
||||
OX_RETURN_ERROR(uuid.toString(writer));
|
||||
return writer.put(';');
|
||||
}
|
||||
|
||||
@@ -26,7 +28,10 @@ ox::Result<T> readAsset(ox::BufferView buff) noexcept {
|
||||
if (!err) {
|
||||
offset = K1HdrSz; // the size of K1 headers
|
||||
}
|
||||
return ox::readClaw<T>(buff + offset);
|
||||
auto out = ox::readClaw<T>(buff + offset);
|
||||
OX_RETURN_ERROR(out);
|
||||
OX_RETURN_ERROR(ensureValid(out.value));
|
||||
return out;
|
||||
}
|
||||
|
||||
ox::Result<ox::ModelObject> readAsset(ox::TypeStore &ts, ox::BufferView buff) noexcept;
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef OX_BARE_METAL
|
||||
#include <functional>
|
||||
#endif
|
||||
|
||||
#include <ox/event/signal.hpp>
|
||||
#include <ox/fs/fs.hpp>
|
||||
#include <ox/model/typenamecatcher.hpp>
|
||||
@@ -151,7 +155,7 @@ class AssetRef: public ox::SignalHandler {
|
||||
private:
|
||||
constexpr ox::Error emitUpdated() const noexcept {
|
||||
updated.emit();
|
||||
return OxError(0);
|
||||
return ox::Error(0);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -178,42 +182,41 @@ constexpr AssetRef<T>::AssetRef(AssetRef &&h) noexcept: m_ctr(h.m_ctr) {
|
||||
h.m_ctr = nullptr;
|
||||
}
|
||||
|
||||
class Context;
|
||||
|
||||
class AssetManager {
|
||||
private:
|
||||
class AssetTypeManagerBase {
|
||||
class AssetTypeManagerBase: public ox::SignalHandler {
|
||||
public:
|
||||
virtual ~AssetTypeManagerBase() = default;
|
||||
~AssetTypeManagerBase() override = default;
|
||||
|
||||
virtual void gc() noexcept = 0;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class AssetTypeManager: public AssetTypeManagerBase {
|
||||
public:
|
||||
using Loader = std::function<ox::Result<T>(ox::StringView assetId)>;
|
||||
private:
|
||||
Loader m_loader{};
|
||||
ox::HashMap<ox::String, ox::UPtr<AssetContainer<T>>> m_cache;
|
||||
|
||||
public:
|
||||
ox::Result<AssetRef<T>> getAsset(ox::StringView const&assetId) const noexcept {
|
||||
auto out = m_cache.at(assetId);
|
||||
oxReturnError(out);
|
||||
return AssetRef<T>(out.value->get());
|
||||
}
|
||||
AssetTypeManager(Loader loader) noexcept: m_loader(loader) {}
|
||||
|
||||
ox::Result<AssetRef<T>> setAsset(ox::StringView const&assetId, T const&obj) noexcept {
|
||||
auto &p = m_cache[assetId];
|
||||
if (!p) {
|
||||
p = ox::make_unique<AssetContainer<T>>(obj);
|
||||
} else {
|
||||
p->set(obj);
|
||||
p->updated.emit();
|
||||
ox::Result<AssetRef<T>> getAsset(ox::StringView const assetId) const noexcept {
|
||||
OX_REQUIRE(out, m_cache.at(assetId));
|
||||
if (!out || !*out) {
|
||||
return ox::Error(1, "asset is null");
|
||||
}
|
||||
return AssetRef<T>(p.get());
|
||||
return AssetRef<T>(out->get());
|
||||
}
|
||||
|
||||
ox::Result<AssetRef<T>> setAsset(ox::StringView const&assetId, T &&obj) noexcept {
|
||||
ox::Result<AssetRef<T>> loadAsset(ox::StringView const assetId) noexcept {
|
||||
auto &p = m_cache[assetId];
|
||||
OX_REQUIRE_M(obj, m_loader(assetId));
|
||||
if (!p) {
|
||||
p = ox::make_unique<AssetContainer<T>>(obj);
|
||||
p = ox::make_unique<AssetContainer<T>>(std::move(obj));
|
||||
} else {
|
||||
p->set(std::move(obj));
|
||||
p->updated.emit();
|
||||
@@ -221,6 +224,18 @@ class AssetManager {
|
||||
return AssetRef<T>(p.get());
|
||||
}
|
||||
|
||||
ox::Error reloadAsset(ox::StringView const assetId) noexcept {
|
||||
auto &p = m_cache[assetId];
|
||||
OX_REQUIRE_M(obj, m_loader(assetId));
|
||||
if (!p) {
|
||||
p = ox::make_unique<AssetContainer<T>>(std::move(obj));
|
||||
} else {
|
||||
p->set(std::move(obj));
|
||||
p->updated.emit();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void gc() noexcept final {
|
||||
for (auto const&ack : m_cache.keys()) {
|
||||
auto &ac = m_cache[ack];
|
||||
@@ -232,29 +247,48 @@ class AssetManager {
|
||||
};
|
||||
|
||||
ox::HashMap<ox::String, ox::UPtr<AssetTypeManagerBase>> m_assetTypeManagers;
|
||||
ox::HashMap<ox::String, ox::Signal<ox::Error(ox::StringView assetId)>> m_fileUpdated;
|
||||
|
||||
template<typename T>
|
||||
AssetTypeManager<T> *getTypeManager() noexcept {
|
||||
constexpr auto typeName = ox::requireModelTypeName<T>();
|
||||
static_assert(ox::StringView(typeName) != "", "Types must have TypeName to use AssetManager");
|
||||
auto &am = m_assetTypeManagers[typeName];
|
||||
if (!am) {
|
||||
am = ox::make_unique<AssetTypeManager<T>>();
|
||||
ox::Result<AssetTypeManager<T>*> getTypeManager() noexcept {
|
||||
constexpr auto &typeId = ox::ModelTypeId_v<T>;
|
||||
static_assert(typeId != "", "Types must have TypeName to use AssetManager");
|
||||
auto &am = m_assetTypeManagers[typeId];
|
||||
auto const out = dynamic_cast<AssetTypeManager<T>*>(am.get());
|
||||
if (!out) {
|
||||
return ox::Error(1, "no AssetTypeManager for type");
|
||||
}
|
||||
return dynamic_cast<AssetTypeManager<T>*>(am.get());
|
||||
return out;
|
||||
}
|
||||
|
||||
public:
|
||||
template<typename T>
|
||||
ox::Result<AssetRef<T>> getAsset(ox::CRStringView assetId) noexcept {
|
||||
auto m = getTypeManager<T>();
|
||||
return m->getAsset(assetId);
|
||||
void initTypeManager(auto const&makeLoader, Context &ctx) noexcept {
|
||||
constexpr auto &typeId = ox::ModelTypeId_v<T>;
|
||||
static_assert(typeId != "", "Types must have TypeName to use AssetManager");
|
||||
auto &am = m_assetTypeManagers[typeId];
|
||||
if (!am) {
|
||||
am = ox::make_unique<AssetTypeManager<T>>(makeLoader(ctx));
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
ox::Result<AssetRef<T>> setAsset(ox::CRStringView assetId, T const&obj) noexcept {
|
||||
auto m = getTypeManager<T>();
|
||||
return m->setAsset(assetId, obj);
|
||||
ox::Result<AssetRef<T>> getAsset(ox::StringView assetId) noexcept {
|
||||
OX_REQUIRE(m, getTypeManager<T>());
|
||||
return m->getAsset(assetId);
|
||||
}
|
||||
|
||||
ox::Error reloadAsset(ox::StringView assetId) noexcept {
|
||||
m_fileUpdated[assetId].emit(assetId);
|
||||
return {};
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
ox::Result<AssetRef<T>> loadAsset(ox::StringView assetId) noexcept {
|
||||
OX_REQUIRE(m, getTypeManager<T>());
|
||||
OX_REQUIRE(out, m->loadAsset(assetId));
|
||||
m_fileUpdated[assetId].connect(m, &AssetTypeManager<T>::reloadAsset);
|
||||
return out;
|
||||
}
|
||||
|
||||
void gc() noexcept {
|
||||
@@ -268,7 +302,7 @@ class AssetManager {
|
||||
template<typename T>
|
||||
class AssetRef {
|
||||
private:
|
||||
T const*const m_obj = nullptr;
|
||||
T const* m_obj = nullptr;
|
||||
|
||||
public:
|
||||
constexpr AssetRef() noexcept = default;
|
||||
|
||||
@@ -17,10 +17,10 @@ namespace keel {
|
||||
ox::Error init(
|
||||
keel::Context &ctx,
|
||||
ox::UPtr<ox::FileSystem> &&fs,
|
||||
ox::CRStringView appName) noexcept;
|
||||
ox::StringViewCR appName) noexcept;
|
||||
|
||||
ox::Result<ox::UPtr<Context>> init(
|
||||
ox::UPtr<ox::FileSystem> &&fs,
|
||||
ox::CRStringView appName) noexcept;
|
||||
ox::StringViewCR appName) noexcept;
|
||||
|
||||
}
|
||||
|
||||
@@ -25,51 +25,88 @@ struct PreloadPtr {
|
||||
uint64_t preloadAddr = 0;
|
||||
};
|
||||
|
||||
oxModelBegin(PreloadPtr)
|
||||
oxModelField(preloadAddr)
|
||||
oxModelEnd()
|
||||
OX_MODEL_BEGIN(PreloadPtr)
|
||||
OX_MODEL_FIELD(preloadAddr)
|
||||
OX_MODEL_END()
|
||||
|
||||
ox::Result<std::size_t> getPreloadAddr(keel::Context &ctx, ox::FileAddress const&file) noexcept;
|
||||
ox::Result<std::size_t> getPreloadAddr(keel::Context &ctx, ox::CRStringView file) noexcept;
|
||||
ox::Result<std::size_t> getPreloadAddr(keel::Context &ctx, ox::FileAddress const&addr) noexcept;
|
||||
ox::Result<std::size_t> getPreloadAddr(keel::Context &ctx, ox::StringViewCR path) noexcept;
|
||||
|
||||
|
||||
void createUuidMapping(Context &ctx, ox::StringViewCR filePath, ox::UUID const&uuid) noexcept;
|
||||
|
||||
ox::Error buildUuidMap(Context &ctx) noexcept;
|
||||
|
||||
ox::Result<ox::UUID> pathToUuid(Context &ctx, ox::StringViewCR path) noexcept;
|
||||
|
||||
ox::Result<ox::UUID> getUuid(Context &ctx, ox::FileAddress const&fileAddr) noexcept;
|
||||
|
||||
ox::Result<ox::UUID> getUuid(Context &ctx, ox::StringViewCR path) noexcept;
|
||||
|
||||
ox::Result<ox::CStringView> getPath(Context &ctx, ox::FileAddress const&fileAddr) noexcept;
|
||||
|
||||
ox::Result<ox::CStringView> getPath(Context &ctx, ox::CStringViewCR fileId) noexcept;
|
||||
|
||||
constexpr ox::Result<ox::UUID> uuidUrlToUuid(ox::StringViewCR uuidUrl) noexcept {
|
||||
return ox::UUID::fromString(substr(uuidUrl, 7));
|
||||
}
|
||||
|
||||
ox::Result<ox::CStringView> uuidUrlToPath(Context &ctx, ox::StringViewCR uuid) noexcept;
|
||||
|
||||
ox::Result<ox::CStringView> uuidToPath(Context &ctx, ox::StringViewCR uuid) noexcept;
|
||||
|
||||
ox::Result<ox::CStringView> uuidToPath(Context &ctx, ox::UUID const&uuid) noexcept;
|
||||
|
||||
#ifndef OX_BARE_METAL
|
||||
|
||||
namespace detail {
|
||||
template<typename T>
|
||||
ox::Result<keel::AssetRef<T>> readObjFile(
|
||||
keel::Context &ctx,
|
||||
ox::StringView assetId,
|
||||
bool forceLoad) noexcept {
|
||||
constexpr auto readConvert = [](Context &ctx, const ox::Buffer &buff) -> ox::Result<T> {
|
||||
constexpr auto makeLoader(Context &ctx) {
|
||||
return [&ctx](ox::StringViewCR assetId) -> ox::Result<T> {
|
||||
OX_REQUIRE(p, ctx.uuidToPath.at(assetId));
|
||||
OX_REQUIRE(buff, ctx.rom->read(*p));
|
||||
auto [obj, err] = readAsset<T>(buff);
|
||||
if (err) {
|
||||
if (err != ox::Error_ClawTypeVersionMismatch && err != ox::Error_ClawTypeMismatch) {
|
||||
return err;
|
||||
}
|
||||
oxReturnError(convert<T>(ctx, buff, &obj));
|
||||
OX_RETURN_ERROR(convert<T>(ctx, buff, &obj));
|
||||
}
|
||||
return std::move(obj);
|
||||
};
|
||||
ox::StringView path;
|
||||
if (beginsWith(assetId, "uuid://")) {
|
||||
assetId = substr(assetId, 7);
|
||||
oxRequire(p, ctx.uuidToPath.at(assetId));
|
||||
path = *p;
|
||||
} else {
|
||||
path = assetId;
|
||||
}
|
||||
if (forceLoad) {
|
||||
oxRequire(buff, ctx.rom->read(path));
|
||||
oxRequire(obj, readConvert(ctx, buff));
|
||||
oxRequire(cached, ctx.assetManager.setAsset(assetId, obj));
|
||||
return cached;
|
||||
} else {
|
||||
auto [cached, err] = ctx.assetManager.getAsset<T>(assetId);
|
||||
if (err) {
|
||||
oxRequire(buff, ctx.rom->read(path));
|
||||
oxRequire(obj, readConvert(ctx, buff));
|
||||
oxReturnError(ctx.assetManager.setAsset(assetId, obj).moveTo(cached));
|
||||
};
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
ox::Result<keel::AssetRef<T>> readObjFile(
|
||||
keel::Context &ctx,
|
||||
ox::StringViewCR assetId,
|
||||
bool const forceLoad) noexcept {
|
||||
static constexpr auto load = [](
|
||||
keel::Context &ctx,
|
||||
ox::StringViewCR assetId,
|
||||
bool forceLoad) -> ox::Result<keel::AssetRef<T>> {
|
||||
if (forceLoad) {
|
||||
ctx.assetManager.initTypeManager<T>(detail::makeLoader<T>, ctx);
|
||||
return ctx.assetManager.loadAsset<T>(assetId);
|
||||
} else {
|
||||
auto [cached, err] = ctx.assetManager.getAsset<T>(assetId);
|
||||
if (err) {
|
||||
ctx.assetManager.initTypeManager<T>(detail::makeLoader<T>, ctx);
|
||||
OX_RETURN_ERROR(ctx.assetManager.loadAsset<T>(assetId).moveTo(cached));
|
||||
}
|
||||
return cached;
|
||||
}
|
||||
};
|
||||
if (beginsWith(assetId, "uuid://")) {
|
||||
return load(ctx, substr(assetId, 7), forceLoad);
|
||||
} else {
|
||||
auto const [uuid, uuidErr] = getUuid(ctx, assetId);
|
||||
if (!uuidErr) {
|
||||
return load(ctx, uuid.toString(), forceLoad);
|
||||
} else {
|
||||
return load(ctx, assetId, forceLoad);
|
||||
}
|
||||
return cached;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,50 +115,23 @@ ox::Result<keel::AssetRef<T>> readObjFile(
|
||||
template<typename T>
|
||||
ox::Result<keel::AssetRef<T>> readObjNoCache(
|
||||
keel::Context &ctx,
|
||||
ox::CRStringView assetId) noexcept {
|
||||
ox::StringViewCR assetId) noexcept {
|
||||
if constexpr(ox::preloadable<T>::value) {
|
||||
oxRequire(addr, getPreloadAddr(ctx, assetId));
|
||||
OX_REQUIRE(addr, getPreloadAddr(ctx, assetId));
|
||||
return keel::AssetRef<T>(std::bit_cast<T const*>(uintptr_t{addr}));
|
||||
} else {
|
||||
return OxError(1);
|
||||
return ox::Error(1);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void createUuidMapping(Context &ctx, ox::StringView filePath, ox::UUID const&uuid) noexcept;
|
||||
|
||||
ox::Error buildUuidMap(Context &ctx) noexcept;
|
||||
|
||||
ox::Result<ox::String> uuidToPath(Context &ctx, ox::CRStringView uuid) noexcept;
|
||||
|
||||
ox::Result<ox::String> uuidToPath(Context &ctx, ox::UUID const&uuid) noexcept;
|
||||
|
||||
ox::Error performPackTransforms(Context &ctx, ox::Buffer &clawData) noexcept;
|
||||
|
||||
template<typename T>
|
||||
ox::Result<AssetRef<T>> setAsset(keel::Context &ctx, ox::StringView assetId, T const&asset) noexcept {
|
||||
#ifndef OX_BARE_METAL
|
||||
if (assetId.len() == 0) {
|
||||
return OxError(1, "Invalid asset ID");
|
||||
}
|
||||
ox::UUIDStr idStr;
|
||||
if (assetId[0] == '/') {
|
||||
auto const [id, err] = ctx.pathToUuid.at(assetId);
|
||||
oxReturnError(err);
|
||||
idStr = id->toString();
|
||||
assetId = idStr;
|
||||
}
|
||||
return ctx.assetManager.setAsset(assetId, asset);
|
||||
#else
|
||||
return OxError(1, "Not supported on this platform");
|
||||
#endif
|
||||
}
|
||||
ox::Error reloadAsset(keel::Context &ctx, ox::StringViewCR assetId) noexcept;
|
||||
|
||||
template<typename T>
|
||||
ox::Result<keel::AssetRef<T>> readObj(
|
||||
keel::Context &ctx,
|
||||
ox::CRStringView assetId,
|
||||
ox::StringViewCR assetId,
|
||||
[[maybe_unused]] bool forceLoad = false) noexcept {
|
||||
#ifndef OX_BARE_METAL
|
||||
return readObjFile<T>(ctx, assetId, forceLoad);
|
||||
@@ -136,14 +146,14 @@ ox::Result<keel::AssetRef<T>> readObj(
|
||||
ox::FileAddress const&file,
|
||||
[[maybe_unused]] bool forceLoad = false) noexcept {
|
||||
#ifndef OX_BARE_METAL
|
||||
oxRequire(assetId, file.getPath());
|
||||
OX_REQUIRE(assetId, file.getPath());
|
||||
return readObj<T>(ctx, ox::StringView(assetId), forceLoad);
|
||||
#else
|
||||
if constexpr(ox::preloadable<T>::value) {
|
||||
oxRequire(addr, getPreloadAddr(ctx, file));
|
||||
OX_REQUIRE(addr, getPreloadAddr(ctx, file));
|
||||
return keel::AssetRef<T>(std::bit_cast<T const*>(uintptr_t{addr}));
|
||||
} else {
|
||||
return OxError(1);
|
||||
return ox::Error(1);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -154,15 +164,15 @@ ox::Error writeObj(
|
||||
ox::FileAddress const&file,
|
||||
T const&obj,
|
||||
ox::ClawFormat fmt = ox::ClawFormat::Metal) noexcept {
|
||||
oxRequire(objBuff, ox::writeClaw(obj, fmt));
|
||||
OX_REQUIRE(objBuff, ox::writeClaw(obj, fmt));
|
||||
return ctx.rom->write(file, objBuff.data(), objBuff.size());
|
||||
}
|
||||
|
||||
ox::Error setRomFs(Context &ctx, ox::UPtr<ox::FileSystem> &&fs) noexcept;
|
||||
|
||||
ox::Result<ox::UniquePtr<ox::FileSystem>> loadRomFs(ox::CRStringView path) noexcept;
|
||||
ox::Result<ox::UPtr<ox::FileSystem>> loadRomFs(ox::StringViewCR path) noexcept;
|
||||
|
||||
ox::Result<char*> loadRom(ox::CRStringView path = "") noexcept;
|
||||
ox::Result<char*> loadRom(ox::StringViewCR path = "") noexcept;
|
||||
|
||||
void unloadRom(char*) noexcept;
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ using TypeDescGenerator = ox::Error(*)(ox::TypeStore&);
|
||||
|
||||
template<typename T>
|
||||
ox::Error generateTypeDesc(ox::TypeStore &ts) noexcept {
|
||||
return ox::buildTypeDef<T>(&ts).error;
|
||||
return ox::buildTypeDef<T>(ts).error;
|
||||
}
|
||||
|
||||
class Module {
|
||||
|
||||
@@ -12,6 +12,31 @@
|
||||
|
||||
namespace keel {
|
||||
|
||||
struct ManifestEntry {
|
||||
static constexpr auto TypeName = "net.drinkingtea.keel.ManifestEntry";
|
||||
static constexpr auto TypeVersion = 1;
|
||||
uint64_t inode{};
|
||||
bool preloaded{};
|
||||
ox::String type;
|
||||
};
|
||||
|
||||
OX_MODEL_BEGIN(ManifestEntry)
|
||||
OX_MODEL_FIELD(inode)
|
||||
OX_MODEL_FIELD(preloaded)
|
||||
OX_MODEL_FIELD(type)
|
||||
OX_MODEL_END()
|
||||
|
||||
struct Manifest {
|
||||
static constexpr auto TypeName = "net.drinkingtea.keel.Manifest";
|
||||
static constexpr auto TypeVersion = 1;
|
||||
ox::HashMap<ox::String, ManifestEntry> files;
|
||||
};
|
||||
|
||||
OX_MODEL_BEGIN(Manifest)
|
||||
OX_MODEL_FIELD(files)
|
||||
OX_MODEL_END()
|
||||
|
||||
|
||||
class Context;
|
||||
|
||||
struct GbaPlatSpec {
|
||||
@@ -86,24 +111,26 @@ ox::Error preloadObj(
|
||||
ox::TypeStore &ts,
|
||||
ox::FileSystem &romFs,
|
||||
ox::Preloader<PlatSpec> &pl,
|
||||
ox::CRStringView path) noexcept {
|
||||
ox::StringView const path) noexcept {
|
||||
// load file
|
||||
oxRequireM(buff, romFs.read(path));
|
||||
oxRequireM(obj, keel::readAsset(ts, buff));
|
||||
OX_REQUIRE_M(buff, romFs.read(path));
|
||||
OX_REQUIRE_M(obj, keel::readAsset(ts, buff));
|
||||
if (obj.type()->preloadable) {
|
||||
oxOutf("preloading {} as a {}\n", path, obj.type()->typeName);
|
||||
// preload
|
||||
oxRequire(a, pl.startAlloc(ox::sizeOf<GbaPlatSpec>(&obj), ox::alignOf<GbaPlatSpec>(obj)));
|
||||
auto const size = ox::sizeOf<GbaPlatSpec>(&obj);
|
||||
auto const alignment = ox::alignOf<GbaPlatSpec>(obj);
|
||||
OX_REQUIRE(a, pl.startAlloc(size, alignment));
|
||||
auto const err = ox::preload<GbaPlatSpec, ox::ModelObject>(&pl, &obj);
|
||||
oxReturnError(pl.endAlloc());
|
||||
oxReturnError(err);
|
||||
keel::PreloadPtr const p{.preloadAddr = static_cast<uint32_t>(a)};
|
||||
oxReturnError(ox::writeMC(p).moveTo(buff));
|
||||
OX_RETURN_ERROR(pl.endAlloc());
|
||||
OX_RETURN_ERROR(err);
|
||||
keel::PreloadPtr const p{.preloadAddr = a};
|
||||
OX_RETURN_ERROR(ox::writeMC(p).moveTo(buff));
|
||||
oxOutf("preloaded {} as a {} @ {} to {}\n", path, obj.type()->typeName, a, a + size);
|
||||
} else {
|
||||
// strip the Claw header (it is not needed after preloading) and write back out to dest fs
|
||||
oxReturnError(ox::writeMC(obj).moveTo(buff));
|
||||
OX_RETURN_ERROR(ox::writeMC(obj).moveTo(buff));
|
||||
}
|
||||
oxReturnError(romFs.write(path, buff.data(), buff.size()));
|
||||
OX_RETURN_ERROR(romFs.write(path, buff.data(), buff.size()));
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -111,25 +138,27 @@ ox::Error preloadObj(
|
||||
// transformations need to be done after the copy to the new FS is complete
|
||||
template<typename PlatSpec>
|
||||
ox::Error preloadDir(
|
||||
Manifest &manifest,
|
||||
ox::TypeStore &ts,
|
||||
ox::FileSystem &romFs,
|
||||
ox::Preloader<PlatSpec> &pl,
|
||||
ox::CRStringView path) noexcept {
|
||||
ox::StringViewCR path) noexcept {
|
||||
// copy
|
||||
oxTracef("pack.preload", "path: {}", path);
|
||||
oxRequire(fileList, romFs.ls(path));
|
||||
OX_REQUIRE(fileList, romFs.ls(path));
|
||||
for (auto const&name : fileList) {
|
||||
auto const filePath = ox::sfmt("{}{}", path, name);
|
||||
oxRequire(stat, romFs.stat(filePath));
|
||||
OX_REQUIRE(stat, romFs.stat(filePath));
|
||||
if (stat.fileType == ox::FileType::Directory) {
|
||||
auto const dir = ox::sfmt("{}{}/", path, name);
|
||||
oxReturnError(preloadDir(ts, romFs, pl, dir));
|
||||
OX_RETURN_ERROR(preloadDir(manifest, ts, romFs, pl, dir));
|
||||
} else {
|
||||
auto const err = preloadObj(ts, romFs, pl, filePath);
|
||||
if (err) {
|
||||
oxErrf("\033[31;1;1mCould not preload {}:\n\t{}\n", filePath, toStr(err));
|
||||
return err;
|
||||
}
|
||||
manifest.files[filePath].preloaded = true;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
@@ -148,23 +177,27 @@ ox::Error appendBinary(ox::Buffer &binBuff, ox::SpanView<char> const&fsBuff, ox:
|
||||
static_assert(mediaHdr.bytes() == hdrSize);
|
||||
static_assert(preloadHdr.bytes() == hdrSize);
|
||||
ox::BufferWriter w(&binBuff);
|
||||
oxReturnError(padbin(w, hdrSize));
|
||||
oxReturnError(w.write(mediaHdr.data(), mediaHdr.bytes()));
|
||||
oxReturnError(w.write(fsBuff.data(), fsBuff.size()));
|
||||
oxReturnError(padbin(w, hdrSize));
|
||||
oxReturnError(w.write(preloadHdr.data(), preloadHdr.bytes()));
|
||||
oxReturnError(pl.offsetPtrs(binBuff.size()));
|
||||
OX_RETURN_ERROR(padbin(w, hdrSize));
|
||||
OX_RETURN_ERROR(w.write(mediaHdr.data(), mediaHdr.bytes()));
|
||||
OX_RETURN_ERROR(w.write(fsBuff.data(), fsBuff.size()));
|
||||
OX_RETURN_ERROR(padbin(w, hdrSize));
|
||||
OX_RETURN_ERROR(w.write(preloadHdr.data(), preloadHdr.bytes()));
|
||||
OX_RETURN_ERROR(pl.offsetPtrs(binBuff.size()));
|
||||
auto const&plBuff = pl.buff();
|
||||
oxReturnError(w.write(plBuff.data(), plBuff.size()));
|
||||
OX_RETURN_ERROR(w.write(plBuff.data(), plBuff.size()));
|
||||
return {};
|
||||
}
|
||||
|
||||
template<typename PlatSpec>
|
||||
ox::Error preload(ox::TypeStore &ts, ox::FileSystem &src, ox::Preloader<PlatSpec> &pl) noexcept {
|
||||
ox::Error preload(
|
||||
Manifest &manifest,
|
||||
ox::TypeStore &ts,
|
||||
ox::FileSystem &src,
|
||||
ox::Preloader<PlatSpec> &pl) noexcept {
|
||||
oxOut("Preloading\n");
|
||||
return detail::preloadDir(ts, src, pl, "/");
|
||||
return detail::preloadDir(manifest, ts, src, pl, "/");
|
||||
}
|
||||
|
||||
ox::Error pack(keel::Context &ctx, ox::TypeStore &ts, ox::FileSystem &dest) noexcept;
|
||||
ox::Error pack(Manifest &manifest, keel::Context &ctx, ox::TypeStore &ts, ox::FileSystem &dest) noexcept;
|
||||
|
||||
}
|
||||
|
||||
@@ -7,8 +7,6 @@
|
||||
#include <ox/std/def.hpp>
|
||||
#include <ox/std/error.hpp>
|
||||
#include <ox/std/string.hpp>
|
||||
#include <ox/claw/read.hpp>
|
||||
#include <ox/claw/write.hpp>
|
||||
|
||||
#include "asset.hpp"
|
||||
#include "context.hpp"
|
||||
@@ -51,29 +49,29 @@ constexpr T &wrapCast(Wrap &ptr) noexcept {
|
||||
|
||||
class BaseConverter {
|
||||
public:
|
||||
virtual ~BaseConverter() noexcept = default;
|
||||
constexpr virtual ~BaseConverter() noexcept = default;
|
||||
|
||||
[[nodiscard]]
|
||||
virtual ox::StringView srcTypeName() const noexcept = 0;
|
||||
constexpr virtual ox::StringView srcTypeName() const noexcept = 0;
|
||||
|
||||
[[nodiscard]]
|
||||
virtual int srcTypeVersion() const noexcept = 0;
|
||||
constexpr virtual int srcTypeVersion() const noexcept = 0;
|
||||
|
||||
[[nodiscard]]
|
||||
virtual bool srcMatches(ox::CRStringView pSrcTypeName, int pSrcTypeVersion) const noexcept = 0;
|
||||
constexpr virtual bool srcMatches(ox::StringViewCR pSrcTypeName, int pSrcTypeVersion) const noexcept = 0;
|
||||
|
||||
[[nodiscard]]
|
||||
virtual bool dstMatches(ox::CRStringView dstTypeName, int dstTypeVersion) const noexcept = 0;
|
||||
constexpr virtual bool dstMatches(ox::StringViewCR dstTypeName, int dstTypeVersion) const noexcept = 0;
|
||||
|
||||
virtual ox::Result<ox::UPtr<Wrap>> convertPtrToPtr(keel::Context &ctx, Wrap &src) const noexcept = 0;
|
||||
|
||||
virtual ox::Result<ox::UPtr<Wrap>> convertBuffToPtr(
|
||||
keel::Context &ctx, ox::Buffer const&srcBuff) const noexcept = 0;
|
||||
keel::Context &ctx, ox::BufferView const&srcBuff) const noexcept = 0;
|
||||
|
||||
[[nodiscard]]
|
||||
inline bool matches(
|
||||
ox::CRStringView srcTypeName, int srcTypeVersion,
|
||||
ox::CRStringView dstTypeName, int dstTypeVersion) const noexcept {
|
||||
constexpr bool matches(
|
||||
ox::StringViewCR srcTypeName, int srcTypeVersion,
|
||||
ox::StringViewCR dstTypeName, int dstTypeVersion) const noexcept {
|
||||
return srcMatches(srcTypeName, srcTypeVersion)
|
||||
&& dstMatches(dstTypeName, dstTypeVersion);
|
||||
}
|
||||
@@ -84,17 +82,17 @@ template<typename SrcType, typename DstType>
|
||||
class Converter: public BaseConverter {
|
||||
public:
|
||||
[[nodiscard]]
|
||||
ox::StringView srcTypeName() const noexcept final {
|
||||
constexpr ox::StringView srcTypeName() const noexcept final {
|
||||
return ox::ModelTypeName_v<SrcType>;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
int srcTypeVersion() const noexcept final {
|
||||
constexpr int srcTypeVersion() const noexcept final {
|
||||
return ox::ModelTypeVersion_v<SrcType>;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
bool srcMatches(ox::CRStringView pSrcTypeName, int pSrcTypeVersion) const noexcept final {
|
||||
constexpr bool srcMatches(ox::StringViewCR pSrcTypeName, int pSrcTypeVersion) const noexcept final {
|
||||
constexpr auto SrcTypeName = ox::requireModelTypeName<SrcType>();
|
||||
constexpr auto SrcTypeVersion = ox::requireModelTypeVersion<SrcType>();
|
||||
return pSrcTypeName == SrcTypeName
|
||||
@@ -102,8 +100,8 @@ class Converter: public BaseConverter {
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
bool dstMatches(ox::CRStringView dstTypeName, int dstTypeVersion) const noexcept final {
|
||||
constexpr auto DstTypeName = ox::StringView(ox::requireModelTypeName<DstType>());
|
||||
constexpr bool dstMatches(ox::StringViewCR dstTypeName, int dstTypeVersion) const noexcept final {
|
||||
constexpr auto DstTypeName = ox::StringView{ox::requireModelTypeName<DstType>()};
|
||||
constexpr auto DstTypeVersion = ox::requireModelTypeVersion<DstType>();
|
||||
return dstTypeName == DstTypeName
|
||||
&& dstTypeVersion == DstTypeVersion;
|
||||
@@ -112,15 +110,15 @@ class Converter: public BaseConverter {
|
||||
ox::Result<ox::UPtr<Wrap>> convertPtrToPtr(
|
||||
keel::Context &ctx, Wrap &src) const noexcept final {
|
||||
auto dst = makeWrap<DstType>();
|
||||
oxReturnError(convert(ctx, wrapCast<SrcType>(src), wrapCast<DstType>(*dst)));
|
||||
OX_RETURN_ERROR(convert(ctx, wrapCast<SrcType>(src), wrapCast<DstType>(*dst)));
|
||||
return {std::move(dst)};
|
||||
}
|
||||
|
||||
ox::Result<ox::UPtr<Wrap>> convertBuffToPtr(
|
||||
keel::Context &ctx, ox::Buffer const&srcBuff) const noexcept final {
|
||||
oxRequireM(src, readAsset<SrcType>(srcBuff));
|
||||
keel::Context &ctx, ox::BufferView const&srcBuff) const noexcept final {
|
||||
OX_REQUIRE_M(src, readAsset<SrcType>(srcBuff));
|
||||
auto dst = makeWrap<DstType>();
|
||||
oxReturnError(convert(ctx, src, wrapCast<DstType>(*dst)));
|
||||
OX_RETURN_ERROR(convert(ctx, src, wrapCast<DstType>(*dst)));
|
||||
return {std::move(dst)};
|
||||
}
|
||||
|
||||
@@ -131,40 +129,40 @@ class Converter: public BaseConverter {
|
||||
|
||||
ox::Result<ox::UPtr<Wrap>> convert(
|
||||
keel::Context &ctx,
|
||||
ox::Buffer const&srcBuffer,
|
||||
ox::CRStringView dstTypeName,
|
||||
ox::BufferView const&srcBuffer,
|
||||
ox::StringViewCR dstTypeName,
|
||||
int dstTypeVersion) noexcept;
|
||||
|
||||
template<typename DstType>
|
||||
ox::Result<DstType> convert(keel::Context &ctx, ox::Buffer const&srcBuffer) noexcept {
|
||||
ox::Result<DstType> convert(keel::Context &ctx, ox::BufferView const&srcBuffer) noexcept {
|
||||
static constexpr auto DstTypeName = ox::requireModelTypeName<DstType>();
|
||||
static constexpr auto DstTypeVersion = ox::requireModelTypeVersion<DstType>();
|
||||
oxRequire(out, convert(ctx, srcBuffer, DstTypeName, DstTypeVersion));
|
||||
OX_REQUIRE(out, convert(ctx, srcBuffer, DstTypeName, DstTypeVersion));
|
||||
return std::move(wrapCast<DstType>(out));
|
||||
}
|
||||
|
||||
template<typename DstType>
|
||||
ox::Error convert(keel::Context &ctx, ox::Buffer const&buff, DstType *outObj) noexcept {
|
||||
ox::Error convert(keel::Context &ctx, ox::BufferView const&buff, DstType *outObj) noexcept {
|
||||
static constexpr auto DstTypeName = ox::requireModelTypeName<DstType>();
|
||||
static constexpr auto DstTypeVersion = ox::requireModelTypeVersion<DstType>();
|
||||
oxRequire(outPtr, convert(ctx, buff, DstTypeName, DstTypeVersion));
|
||||
OX_REQUIRE(outPtr, convert(ctx, buff, DstTypeName, DstTypeVersion));
|
||||
*outObj = std::move(wrapCast<DstType>(*outPtr));
|
||||
return {};
|
||||
}
|
||||
|
||||
template<typename DstType>
|
||||
ox::Result<ox::Buffer> convertBuffToBuff(
|
||||
keel::Context &ctx, ox::Buffer const&srcBuffer, ox::ClawFormat fmt) noexcept {
|
||||
keel::Context &ctx, ox::BufferView const&srcBuffer, ox::ClawFormat fmt) noexcept {
|
||||
static constexpr auto DstTypeName = ox::requireModelTypeName<DstType>();
|
||||
static constexpr auto DstTypeVersion = ox::requireModelTypeVersion<DstType>();
|
||||
oxRequire(out, convert(ctx, srcBuffer, DstTypeName, DstTypeVersion));
|
||||
OX_REQUIRE(out, convert(ctx, srcBuffer, DstTypeName, DstTypeVersion));
|
||||
return ox::writeClaw<DstType>(wrapCast<DstType>(*out), fmt);
|
||||
}
|
||||
|
||||
template<typename From, typename To, ox::ClawFormat fmt = ox::ClawFormat::Metal>
|
||||
ox::Result<bool> transformRule(keel::Context &ctx, ox::Buffer &buff, ox::StringView typeId) noexcept {
|
||||
if (typeId == ox::ModelTypeId_v<From>) {
|
||||
oxReturnError(keel::convertBuffToBuff<To>(ctx, buff, fmt).moveTo(buff));
|
||||
OX_RETURN_ERROR(keel::convertBuffToBuff<To>(ctx, buff, fmt).moveTo(buff));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
27
src/olympic/keel/include/keel/validation.hpp
Normal file
27
src/olympic/keel/include/keel/validation.hpp
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2016 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/std/error.hpp>
|
||||
|
||||
namespace keel {
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr bool valid(auto const&) noexcept {
|
||||
return true;
|
||||
}
|
||||
|
||||
constexpr ox::Error repair(auto const&) noexcept {
|
||||
return ox::Error(1, "No repair function for this type");
|
||||
}
|
||||
|
||||
constexpr ox::Error ensureValid(auto &o) noexcept {
|
||||
if (!valid(o)) {
|
||||
return repair(o);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,13 +8,13 @@ namespace keel {
|
||||
|
||||
ox::Result<ox::UUID> readUuidHeader(ox::BufferView buff) noexcept {
|
||||
if (buff.size() < K1HdrSz) [[unlikely]] {
|
||||
return OxError(1, "Insufficient data to contain complete Keel header");
|
||||
return ox::Error(1, "Insufficient data to contain complete Keel header");
|
||||
}
|
||||
constexpr ox::StringView k1Hdr = "K1;";
|
||||
if (k1Hdr != ox::StringView(buff.data(), k1Hdr.bytes())) [[unlikely]] {
|
||||
return OxError(2, "No Keel asset header data");
|
||||
return ox::Error(2, "No Keel asset header data");
|
||||
}
|
||||
return ox::UUID::fromString(ox::StringView(buff.data() + k1Hdr.bytes(), 36));
|
||||
return ox::UUID::fromString(ox::StringView(&buff[k1Hdr.bytes()], 36));
|
||||
}
|
||||
|
||||
ox::Result<ox::ModelObject> readAsset(ox::TypeStore &ts, ox::BufferView buff) noexcept {
|
||||
@@ -30,7 +30,7 @@ ox::Result<ox::StringView> readAssetTypeId(ox::BufferView buff) noexcept {
|
||||
const auto err = readUuidHeader(buff).error;
|
||||
const auto offset = err ? 0u : K1HdrSz;
|
||||
if (offset >= buff.size()) [[unlikely]] {
|
||||
return OxError(1, "Buffer too small for expected data");
|
||||
return ox::Error(1, "Buffer too small for expected data");
|
||||
}
|
||||
return ox::readClawTypeId(buff + offset);
|
||||
}
|
||||
@@ -40,10 +40,10 @@ ox::Result<AssetHdr> readAssetHeader(ox::BufferView buff) noexcept {
|
||||
const auto err = readUuidHeader(buff).moveTo(out.value.uuid);
|
||||
const auto offset = err ? 0u : K1HdrSz;
|
||||
if (offset >= buff.size()) [[unlikely]] {
|
||||
return OxError(1, "Buffer too small for expected data");
|
||||
return ox::Error(1, "Buffer too small for expected data");
|
||||
}
|
||||
buff += offset;
|
||||
oxReturnError(ox::readClawHeader(buff).moveTo(out.value.clawHdr));
|
||||
OX_RETURN_ERROR(ox::readClawHeader(buff).moveTo(out.value.clawHdr));
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace keel {
|
||||
ox::Error init(
|
||||
keel::Context &ctx,
|
||||
ox::UPtr<ox::FileSystem> &&fs,
|
||||
ox::CRStringView appName) noexcept {
|
||||
ox::StringViewCR appName) noexcept {
|
||||
ctx.appName = appName;
|
||||
std::ignore = setRomFs(ctx, std::move(fs));
|
||||
#ifndef OX_BARE_METAL
|
||||
@@ -28,9 +28,9 @@ ox::Error init(
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Result<ox::UPtr<Context>> init(ox::UPtr<ox::FileSystem> &&fs, ox::CRStringView appName) noexcept {
|
||||
ox::Result<ox::UPtr<Context>> init(ox::UPtr<ox::FileSystem> &&fs, ox::StringViewCR appName) noexcept {
|
||||
auto ctx = ox::make_unique<Context>();
|
||||
oxReturnError(keel::init(*ctx, std::move(fs), appName));
|
||||
OX_RETURN_ERROR(keel::init(*ctx, std::move(fs), appName));
|
||||
return ctx;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,11 +12,11 @@
|
||||
|
||||
namespace keel {
|
||||
|
||||
ox::Result<char*> loadRom(ox::CRStringView path) noexcept {
|
||||
ox::Result<char*> loadRom(ox::StringViewCR path) noexcept {
|
||||
std::ifstream file(std::string(toStdStringView(path)), std::ios::binary | std::ios::ate);
|
||||
if (!file.good()) {
|
||||
oxErrorf("Could not find ROM file: {}", path);
|
||||
return OxError(1, "Could not find ROM file");
|
||||
return ox::Error(1, "Could not find ROM file");
|
||||
}
|
||||
try {
|
||||
auto const size = file.tellg();
|
||||
@@ -26,10 +26,10 @@ ox::Result<char*> loadRom(ox::CRStringView path) noexcept {
|
||||
return buff;
|
||||
} catch (std::ios_base::failure const&e) {
|
||||
oxErrorf("Could not read ROM file due to file IO failure: {}", e.what());
|
||||
return OxError(2, "Could not read ROM file");
|
||||
return ox::Error(2, "Could not read ROM file");
|
||||
} catch (std::bad_alloc const&e) {
|
||||
oxErrorf("Could not read ROM file due to new failure: {}", e.what());
|
||||
return OxError(2, "Could not allocate memory for ROM file");
|
||||
return ox::Error(2, "Could not allocate memory for ROM file");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,25 +42,25 @@ static void clearUuidMap(Context &ctx) noexcept {
|
||||
ctx.pathToUuid.clear();
|
||||
}
|
||||
|
||||
void createUuidMapping(Context &ctx, ox::StringView filePath, ox::UUID const&uuid) noexcept {
|
||||
void createUuidMapping(Context &ctx, ox::StringViewCR filePath, ox::UUID const&uuid) noexcept {
|
||||
ctx.pathToUuid[filePath] = uuid;
|
||||
ctx.uuidToPath[uuid.toString()] = filePath;
|
||||
}
|
||||
|
||||
static ox::Error buildUuidMap(Context &ctx, ox::CRStringView path) noexcept {
|
||||
oxRequire(files, ctx.rom->ls(path));
|
||||
static ox::Error buildUuidMap(Context &ctx, ox::StringViewCR path) noexcept {
|
||||
OX_REQUIRE(files, ctx.rom->ls(path));
|
||||
for (auto const&f : files) {
|
||||
oxRequireM(filePath, ox::join("/", ox::Array<ox::StringView, 2>{path, f}));
|
||||
oxRequire(stat, ctx.rom->stat(filePath));
|
||||
OX_REQUIRE_M(filePath, ox::join("/", ox::Array<ox::StringView, 2>{path, f}));
|
||||
OX_REQUIRE(stat, ctx.rom->stat(filePath));
|
||||
if (stat.fileType == ox::FileType::NormalFile) {
|
||||
oxRequire(data, ctx.rom->read(filePath));
|
||||
OX_REQUIRE(data, ctx.rom->read(filePath));
|
||||
auto const [hdr, err] = readAssetHeader(data);
|
||||
if (!err) {
|
||||
createUuidMapping(ctx, filePath, hdr.uuid);
|
||||
}
|
||||
} else if (stat.fileType == ox::FileType::Directory) {
|
||||
if (!beginsWith(f, ".")) {
|
||||
oxReturnError(buildUuidMap(ctx, filePath));
|
||||
OX_RETURN_ERROR(buildUuidMap(ctx, filePath));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,48 +69,102 @@ static ox::Error buildUuidMap(Context &ctx, ox::CRStringView path) noexcept {
|
||||
|
||||
ox::Error buildUuidMap(Context &ctx) noexcept {
|
||||
if (!ctx.rom) {
|
||||
return OxError(1, "No ROM FS");
|
||||
return ox::Error(1, "No ROM FS");
|
||||
}
|
||||
return buildUuidMap(ctx, "");
|
||||
}
|
||||
|
||||
ox::Result<ox::UUID> pathToUuid(Context &ctx, ox::CRStringView path) noexcept {
|
||||
ox::Result<ox::UUID> pathToUuid(Context &ctx, ox::StringViewCR path) noexcept {
|
||||
#ifndef OX_BARE_METAL
|
||||
oxRequire(out, ctx.pathToUuid.at(path));
|
||||
OX_REQUIRE(out, ctx.pathToUuid.at(path));
|
||||
return *out;
|
||||
#else
|
||||
return OxError(1, "UUID to path conversion not supported on this platform");
|
||||
return ox::Error(1, "UUID to path conversion not supported on this platform");
|
||||
#endif
|
||||
}
|
||||
|
||||
ox::Result<ox::String> uuidToPath(Context &ctx, ox::CRStringView uuid) noexcept {
|
||||
ox::Result<ox::UUID> getUuid(Context &ctx, ox::FileAddress const&fileAddr) noexcept {
|
||||
OX_REQUIRE(path, fileAddr.getPath());
|
||||
return getUuid(ctx, path);
|
||||
}
|
||||
|
||||
ox::Result<ox::UUID> getUuid(Context &ctx, ox::StringViewCR path) noexcept {
|
||||
if (beginsWith(path, "uuid://")) {
|
||||
auto const uuid = substr(path, 7);
|
||||
return ox::UUID::fromString(uuid);
|
||||
} else {
|
||||
return pathToUuid(ctx, path);
|
||||
}
|
||||
}
|
||||
|
||||
ox::Result<ox::CStringView> getPath(Context &ctx, ox::FileAddress const&fileAddr) noexcept {
|
||||
OX_REQUIRE(path, fileAddr.getPath());
|
||||
if (beginsWith(path, "uuid://")) {
|
||||
auto const uuid = substr(path, 7);
|
||||
#ifndef OX_BARE_METAL
|
||||
oxRequire(out, ctx.uuidToPath.at(uuid));
|
||||
return *out;
|
||||
OX_REQUIRE_M(out, ctx.uuidToPath.at(uuid));
|
||||
return ox::CStringView{*out};
|
||||
#else
|
||||
return OxError(1, "UUID to path conversion not supported on this platform");
|
||||
return ox::Error(1, "UUID to path conversion not supported on this platform");
|
||||
#endif
|
||||
} else {
|
||||
return ox::CStringView{path};
|
||||
}
|
||||
}
|
||||
|
||||
ox::Result<ox::CStringView> getPath(Context &ctx, ox::CStringViewCR fileId) noexcept {
|
||||
if (beginsWith(fileId, "uuid://")) {
|
||||
auto const uuid = substr(fileId, 7);
|
||||
#ifndef OX_BARE_METAL
|
||||
OX_REQUIRE_M(out, ctx.uuidToPath.at(uuid));
|
||||
return ox::CStringView{*out};
|
||||
#else
|
||||
return ox::Error(1, "UUID to path conversion not supported on this platform");
|
||||
#endif
|
||||
} else {
|
||||
return ox::CStringView{fileId};
|
||||
}
|
||||
}
|
||||
|
||||
ox::Result<ox::CStringView> uuidUrlToPath(Context &ctx, ox::StringViewCR uuid) noexcept {
|
||||
#ifndef OX_BARE_METAL
|
||||
OX_REQUIRE_M(out, ctx.uuidToPath.at(substr(uuid, 7)));
|
||||
return ox::CStringView(*out);
|
||||
#else
|
||||
return ox::Error(1, "UUID to path conversion not supported on this platform");
|
||||
#endif
|
||||
}
|
||||
|
||||
ox::Result<ox::String> uuidToPath(Context &ctx, ox::UUID const&uuid) noexcept {
|
||||
ox::Result<ox::CStringView> uuidToPath(Context &ctx, ox::StringViewCR uuid) noexcept {
|
||||
#ifndef OX_BARE_METAL
|
||||
oxRequire(out, ctx.uuidToPath.at(uuid.toString()));
|
||||
return *out;
|
||||
OX_REQUIRE_M(out, ctx.uuidToPath.at(uuid));
|
||||
return ox::CStringView(*out);
|
||||
#else
|
||||
return OxError(1, "UUID to path conversion not supported on this platform");
|
||||
return ox::Error(1, "UUID to path conversion not supported on this platform");
|
||||
#endif
|
||||
}
|
||||
|
||||
ox::Error performPackTransforms(Context &ctx, ox::Buffer &clawData) noexcept {
|
||||
oxRequireM(typeId, readAssetTypeId(clawData));
|
||||
for (auto const tr : packTransforms(ctx)) {
|
||||
bool changed{};
|
||||
oxReturnError(tr(ctx, clawData, typeId).moveTo(changed));
|
||||
if (changed) {
|
||||
oxReturnError(readAssetTypeId(clawData).moveTo(typeId));
|
||||
ox::Result<ox::CStringView> uuidToPath(Context &ctx, ox::UUID const&uuid) noexcept {
|
||||
#ifndef OX_BARE_METAL
|
||||
OX_REQUIRE_M(out, ctx.uuidToPath.at(uuid.toString()));
|
||||
return ox::CStringView(*out);
|
||||
#else
|
||||
return ox::Error(1, "UUID to path conversion not supported on this platform");
|
||||
#endif
|
||||
}
|
||||
|
||||
ox::Error reloadAsset(keel::Context &ctx, ox::StringViewCR assetId) noexcept {
|
||||
ox::UUIDStr uuidStr;
|
||||
if (beginsWith(assetId, "uuid://")) {
|
||||
return ctx.assetManager.reloadAsset(substr(assetId, 7));
|
||||
} else {
|
||||
auto const [uuid, uuidErr] = getUuid(ctx, assetId);
|
||||
if (!uuidErr) {
|
||||
return ctx.assetManager.reloadAsset(uuidStr);
|
||||
} else {
|
||||
return ctx.assetManager.reloadAsset(assetId);
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -130,7 +184,7 @@ ox::Error buildUuidMap(Context&) noexcept {
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Result<char*> loadRom(ox::CRStringView) noexcept {
|
||||
ox::Result<char*> loadRom(ox::StringViewCR) noexcept {
|
||||
// put the header in the wrong order to prevent mistaking this code for the
|
||||
// media section
|
||||
constexpr auto headerP2 = "R_______________";
|
||||
@@ -144,28 +198,32 @@ ox::Result<char*> loadRom(ox::CRStringView) noexcept {
|
||||
return current + headerLen;
|
||||
}
|
||||
}
|
||||
return OxError(1);
|
||||
return ox::Error(1);
|
||||
}
|
||||
|
||||
void unloadRom(char*) noexcept {
|
||||
}
|
||||
|
||||
ox::Result<std::size_t> getPreloadAddr(keel::Context &ctx, ox::CRStringView path) noexcept {
|
||||
oxRequire(stat, ctx.rom->stat(path));
|
||||
oxRequire(buff, static_cast<ox::MemFS*>(ctx.rom.get())->directAccess(path));
|
||||
ox::Result<std::size_t> getPreloadAddr(keel::Context &ctx, ox::StringViewCR path) noexcept {
|
||||
OX_REQUIRE(stat, ctx.rom->stat(path));
|
||||
OX_REQUIRE(buff, static_cast<ox::MemFS&>(*ctx.rom).directAccess(path));
|
||||
PreloadPtr p;
|
||||
oxReturnError(ox::readMC({buff, static_cast<std::size_t>(stat.size)}, p));
|
||||
OX_RETURN_ERROR(ox::readMC({buff, static_cast<std::size_t>(stat.size)}, p));
|
||||
return static_cast<std::size_t>(p.preloadAddr) + ctx.preloadSectionOffset;
|
||||
}
|
||||
|
||||
ox::Result<std::size_t> getPreloadAddr(keel::Context &ctx, ox::FileAddress const&file) noexcept {
|
||||
oxRequire(stat, ctx.rom->stat(file));
|
||||
oxRequire(buff, static_cast<ox::MemFS*>(ctx.rom.get())->directAccess(file));
|
||||
ox::Result<std::size_t> getPreloadAddr(keel::Context &ctx, ox::FileAddress const&addr) noexcept {
|
||||
OX_REQUIRE(stat, ctx.rom->stat(addr));
|
||||
OX_REQUIRE(buff, static_cast<ox::MemFS&>(*ctx.rom).directAccess(addr));
|
||||
PreloadPtr p;
|
||||
oxReturnError(ox::readMC({buff, static_cast<std::size_t>(stat.size)}, p));
|
||||
OX_RETURN_ERROR(ox::readMC({buff, static_cast<std::size_t>(stat.size)}, p));
|
||||
return static_cast<std::size_t>(p.preloadAddr) + ctx.preloadSectionOffset;
|
||||
}
|
||||
|
||||
ox::Error reloadAsset(keel::Context&, ox::StringView) noexcept {
|
||||
return ox::Error(1, "reloadAsset is unsupported on this platform");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -178,16 +236,16 @@ ox::Error setRomFs(Context &ctx, ox::UPtr<ox::FileSystem> &&fs) noexcept {
|
||||
return buildUuidMap(ctx);
|
||||
}
|
||||
|
||||
ox::Result<ox::UniquePtr<ox::FileSystem>> loadRomFs(ox::CRStringView path) noexcept {
|
||||
ox::Result<ox::UPtr<ox::FileSystem>> loadRomFs(ox::StringViewCR path) noexcept {
|
||||
auto const lastDot = ox::lastIndexOf(path, '.');
|
||||
if (!lastDot.error && substr(path, lastDot.value) == ".oxfs") {
|
||||
oxRequire(rom, loadRom(path));
|
||||
OX_REQUIRE(rom, loadRom(path));
|
||||
return {ox::make_unique<ox::FileSystem32>(rom, 32 * ox::units::MB, unloadRom)};
|
||||
} else {
|
||||
#ifdef OX_HAS_PASSTHROUGHFS
|
||||
return {ox::make_unique<ox::PassThroughFS>(path)};
|
||||
#else
|
||||
return OxError(2);
|
||||
return ox::Error(2);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,9 @@ namespace keel {
|
||||
static ox::Vector<Module const*> mods;
|
||||
|
||||
void registerModule(Module const*mod) noexcept {
|
||||
mods.emplace_back(mod);
|
||||
if (mod) {
|
||||
mods.emplace_back(mod);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
|
||||
@@ -8,15 +8,16 @@
|
||||
#include <ox/fs/fs.hpp>
|
||||
#include <ox/logconn/def.hpp>
|
||||
#include <ox/logconn/logconn.hpp>
|
||||
#include <ox/oc/write.hpp>
|
||||
|
||||
#include <keel/keel.hpp>
|
||||
|
||||
static ox::Error writeFileBuff(ox::StringView path, ox::Buffer const&buff) noexcept {
|
||||
static ox::Error writeFileBuff(ox::StringView path, ox::BufferView const buff) noexcept {
|
||||
try {
|
||||
std::ofstream f(std::string(toStdStringView(path)), std::ios::binary);
|
||||
f.write(buff.data(), static_cast<intptr_t>(buff.size()));
|
||||
} catch (std::fstream::failure const&) {
|
||||
return OxError(2, "failed to write file");
|
||||
return ox::Error(2, "failed to write file");
|
||||
}
|
||||
return {};
|
||||
}
|
||||
@@ -25,7 +26,7 @@ static ox::Result<ox::Buffer> readFileBuff(ox::StringView path) noexcept {
|
||||
std::ifstream file(std::string(toStdStringView(path)), std::ios::binary | std::ios::ate);
|
||||
if (!file.good()) {
|
||||
oxErrorf("Could not find OxFS file: {}", path);
|
||||
return OxError(1, "Could not find OxFS file");
|
||||
return ox::Error(1, "Could not find OxFS file");
|
||||
}
|
||||
try {
|
||||
auto const size = static_cast<std::size_t>(file.tellg());
|
||||
@@ -35,69 +36,67 @@ static ox::Result<ox::Buffer> readFileBuff(ox::StringView path) noexcept {
|
||||
return buff;
|
||||
} catch (std::ios_base::failure const&e) {
|
||||
oxErrorf("Could not read OxFS file: {}", e.what());
|
||||
return OxError(2, "Could not read OxFS file");
|
||||
return ox::Error(2, "Could not read OxFS file");
|
||||
}
|
||||
}
|
||||
|
||||
static ox::Error generateTypes(ox::TypeStore *ts) noexcept {
|
||||
static ox::Error generateTypes(ox::TypeStore &ts) noexcept {
|
||||
for (auto const mod : keel::modules()) {
|
||||
for (auto gen : mod->types()) {
|
||||
oxReturnError(gen(*ts));
|
||||
OX_RETURN_ERROR(gen(ts));
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static ox::Error pack(ox::StringView argSrc, ox::StringView argRomBin, ox::StringView projectDataDir) noexcept {
|
||||
static ox::Error pack(
|
||||
ox::StringView argSrc,
|
||||
ox::StringView argRomBin,
|
||||
ox::StringView argManifest,
|
||||
ox::StringView projectDataDir) noexcept {
|
||||
ox::Buffer dstBuff(32 * ox::units::MB);
|
||||
oxReturnError(ox::FileSystem32::format(dstBuff.data(), dstBuff.size()));
|
||||
OX_RETURN_ERROR(ox::FileSystem32::format(dstBuff.data(), dstBuff.size()));
|
||||
ox::FileSystem32 dst(dstBuff);
|
||||
oxRequire(ctx, keel::init(ox::make_unique<ox::PassThroughFS>(argSrc), "keel-pack"));
|
||||
OX_REQUIRE(ctx, keel::init(ox::make_unique<ox::PassThroughFS>(argSrc), "keel-pack"));
|
||||
keel::TypeStore ts(*ctx->rom, ox::sfmt("{}/type_descriptors", projectDataDir));
|
||||
oxReturnError(generateTypes(&ts));
|
||||
oxReturnError(keel::pack(*ctx, ts, dst));
|
||||
oxRequireM(pl, keel::GbaPreloader::make());
|
||||
oxReturnError(preload(ts, dst, *pl));
|
||||
oxReturnError(dst.resize());
|
||||
OX_RETURN_ERROR(generateTypes(ts));
|
||||
keel::Manifest manifest;
|
||||
OX_RETURN_ERROR(keel::pack(manifest, *ctx, ts, dst));
|
||||
OX_REQUIRE_M(pl, keel::GbaPreloader::make());
|
||||
OX_RETURN_ERROR(preload(manifest, ts, dst, *pl));
|
||||
OX_RETURN_ERROR(dst.resize());
|
||||
// resize buffer
|
||||
oxRequire(dstSize, dst.size());
|
||||
OX_REQUIRE(dstSize, dst.size());
|
||||
dstBuff.resize(dstSize);
|
||||
|
||||
oxRequireM(romBuff, readFileBuff(argRomBin));
|
||||
oxReturnError(appendBinary(romBuff, dstBuff, *pl));
|
||||
|
||||
// concatenate ROM segments
|
||||
OX_REQUIRE_M(romBuff, readFileBuff(argRomBin));
|
||||
oxOutf("Input exe size: {} bytes\n", romBuff.size());
|
||||
oxOutf("Dest FS size: {} bytes\n", dstSize);
|
||||
oxOutf("Preload buff size: {} bytes\n", pl->buff().size());
|
||||
oxOutf("ROM buff size: {} bytes\n", romBuff.size());
|
||||
|
||||
oxReturnError(writeFileBuff(argRomBin, romBuff));
|
||||
OX_RETURN_ERROR(appendBinary(romBuff, dstBuff, *pl));
|
||||
oxOutf("Final ROM buff size: {} bytes\n", romBuff.size());
|
||||
OX_RETURN_ERROR(writeFileBuff(argRomBin, romBuff));
|
||||
OX_REQUIRE(manifestJson, ox::writeOCString(manifest));
|
||||
OX_RETURN_ERROR(writeFileBuff(argManifest, {manifestJson.data(), manifestJson.len()}));
|
||||
return {};
|
||||
}
|
||||
|
||||
static ox::Error run(int argc, char const**argv, ox::StringView projectDataDir) noexcept {
|
||||
ox::ClArgs const args(argc, argv);
|
||||
auto const argSrc = args.getString("src", "");
|
||||
auto const argRomBin = args.getString("rom-bin", "");
|
||||
if (argSrc == "") {
|
||||
oxErr("\033[31;1;1merror:\033[0m must specify a source directory\n");
|
||||
return OxError(1, "must specify a source directory");
|
||||
}
|
||||
if (argRomBin == "") {
|
||||
oxErr("\033[31;1;1merror:\033[0m must specify a path for ROM file\n");
|
||||
return OxError(1, "must specify a path for preload file");
|
||||
}
|
||||
return pack(argSrc, argRomBin, projectDataDir);
|
||||
}
|
||||
|
||||
namespace olympic {
|
||||
|
||||
ox::Error run(
|
||||
[[maybe_unused]] ox::StringView project,
|
||||
[[maybe_unused]] ox::StringView appName,
|
||||
ox::StringView projectDataDir,
|
||||
int argc,
|
||||
char const**argv) noexcept {
|
||||
return ::run(argc, argv, projectDataDir);
|
||||
}
|
||||
|
||||
ox::SpanView<ox::CString> argv) noexcept {
|
||||
ox::ClArgs const args(argv);
|
||||
auto const argSrc = args.getString("src", "");
|
||||
auto const argRomBin = args.getString("rom-bin", "");
|
||||
auto const argManifest = args.getString("manifest", "");
|
||||
if (argSrc == "") {
|
||||
oxErr("\033[31;1;1merror:\033[0m must specify a source directory\n");
|
||||
return ox::Error(1, "must specify a source directory");
|
||||
}
|
||||
if (argRomBin == "") {
|
||||
oxErr("\033[31;1;1merror:\033[0m must specify a path for ROM file\n");
|
||||
return ox::Error(1, "must specify a path for preload file");
|
||||
}
|
||||
return pack(argSrc, argRomBin, argManifest, projectDataDir);
|
||||
}
|
||||
|
||||
@@ -6,35 +6,42 @@
|
||||
#include <ox/model/modelvalue.hpp>
|
||||
|
||||
#include <keel/media.hpp>
|
||||
|
||||
#include <keel/pack.hpp>
|
||||
|
||||
namespace keel {
|
||||
|
||||
static ox::Error pathToInode(
|
||||
keel::Context &ctx, ox::FileSystem &dest, ox::ModelObject &obj) noexcept {
|
||||
keel::Context &ctx,
|
||||
ox::FileSystem &dest,
|
||||
ox::ModelObject &obj) noexcept {
|
||||
auto &o = obj;
|
||||
auto type = static_cast<ox::FileAddressType>(o.at("type").unwrap()->get<int8_t>());
|
||||
auto &data = o.at("data").unwrap()->get<ox::ModelUnion>();
|
||||
OX_REQUIRE(typeVal, o.at("type"));
|
||||
auto const type = static_cast<ox::FileAddressType>(typeVal->get<int8_t>());
|
||||
OX_REQUIRE(dataVal, o.at("data"));
|
||||
auto &data = dataVal->get<ox::ModelUnion>();
|
||||
ox::String path;
|
||||
switch (type) {
|
||||
case ox::FileAddressType::Path:
|
||||
path = data.at("path").unwrap()->get<ox::String>();
|
||||
case ox::FileAddressType::Path: {
|
||||
OX_REQUIRE(pathVal, data.at("path"));
|
||||
path = pathVal->get<ox::String>();
|
||||
break;
|
||||
case ox::FileAddressType::ConstPath:
|
||||
path = data.at("constPath").unwrap()->get<ox::String>();
|
||||
}
|
||||
case ox::FileAddressType::ConstPath: {
|
||||
OX_REQUIRE(pathVal, data.at("constPath"));
|
||||
path = pathVal->get<ox::String>();
|
||||
break;
|
||||
}
|
||||
case ox::FileAddressType::Inode:
|
||||
case ox::FileAddressType::None:
|
||||
return {};
|
||||
}
|
||||
if (beginsWith(path, "uuid://")) {
|
||||
auto const uuid = ox::substr(path, 7);
|
||||
oxReturnError(keel::uuidToPath(ctx, uuid).moveTo(path));
|
||||
OX_RETURN_ERROR(keel::uuidToPath(ctx, uuid).to<ox::String>().moveTo(path));
|
||||
}
|
||||
oxRequire(s, dest.stat(path));
|
||||
oxReturnError(o.at("type").unwrap()->set(static_cast<int8_t>(ox::FileAddressType::Inode)));
|
||||
oxOutf("path to inode: {} => {}\n", path, s.inode);
|
||||
OX_REQUIRE(s, dest.stat(path));
|
||||
OX_RETURN_ERROR(typeVal->set(static_cast<int8_t>(ox::FileAddressType::Inode)));
|
||||
oxOutf("\tpath to inode: {} => {}\n", path, s.inode);
|
||||
return data.set(2, s.inode);
|
||||
}
|
||||
|
||||
@@ -62,7 +69,7 @@ static ox::Error transformFileAddressesVec(
|
||||
ox::FileSystem &dest,
|
||||
ox::ModelValueVector &v) noexcept {
|
||||
for (auto &f : v) {
|
||||
oxReturnError(transformFileAddresses(ctx, dest, f));
|
||||
OX_RETURN_ERROR(transformFileAddresses(ctx, dest, f));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
@@ -80,48 +87,67 @@ static ox::Error transformFileAddressesObj(
|
||||
}
|
||||
for (auto &f : obj) {
|
||||
auto &v = f->value;
|
||||
oxReturnError(transformFileAddresses(ctx, dest, v));
|
||||
OX_RETURN_ERROR(transformFileAddresses(ctx, dest, v));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static ox::Error performPackTransforms(
|
||||
ManifestEntry &entry,
|
||||
Context &ctx,
|
||||
ox::Buffer &clawData) noexcept {
|
||||
OX_REQUIRE_M(typeId, readAssetTypeId(clawData));
|
||||
for (auto const tr : packTransforms(ctx)) {
|
||||
bool changed{};
|
||||
OX_RETURN_ERROR(tr(ctx, clawData, typeId).moveTo(changed));
|
||||
if (changed) {
|
||||
OX_RETURN_ERROR(readAssetTypeId(clawData).moveTo(typeId));
|
||||
}
|
||||
}
|
||||
entry.type = ox::String{typeId};
|
||||
return {};
|
||||
}
|
||||
|
||||
static ox::Error doTransformations(
|
||||
Manifest &manifest,
|
||||
keel::Context &ctx,
|
||||
ox::TypeStore &ts,
|
||||
ox::FileSystem &dest,
|
||||
ox::CRStringView filePath) noexcept {
|
||||
ox::StringViewCR filePath) noexcept {
|
||||
// load file
|
||||
oxRequire(s, dest.stat(filePath));
|
||||
OX_REQUIRE(s, dest.stat(filePath));
|
||||
// do transformations
|
||||
oxRequireM(buff, dest.read(s.inode));
|
||||
oxReturnError(keel::performPackTransforms(ctx, buff));
|
||||
OX_REQUIRE_M(buff, dest.read(s.inode));
|
||||
OX_RETURN_ERROR(keel::performPackTransforms(manifest.files[filePath], ctx, buff));
|
||||
// transform FileAddresses
|
||||
oxRequireM(obj, keel::readAsset(ts, buff));
|
||||
oxReturnError(transformFileAddressesObj(ctx, dest, obj));
|
||||
oxReturnError(ox::writeClaw(obj).moveTo(buff));
|
||||
OX_REQUIRE_M(obj, keel::readAsset(ts, buff));
|
||||
oxOutf("transforming {}\n", filePath);
|
||||
OX_RETURN_ERROR(transformFileAddressesObj(ctx, dest, obj));
|
||||
OX_RETURN_ERROR(ox::writeClaw(obj).moveTo(buff));
|
||||
// write file to dest
|
||||
oxReturnError(dest.write(s.inode, buff));
|
||||
OX_RETURN_ERROR(dest.write(s.inode, buff));
|
||||
return {};
|
||||
}
|
||||
|
||||
// claw file transformations are broken out from copy because path to inode
|
||||
// transformations need to be done after the copy to the new FS is complete
|
||||
static ox::Error transformClaw(
|
||||
Manifest &manifest,
|
||||
keel::Context &ctx,
|
||||
ox::TypeStore &ts,
|
||||
ox::FileSystem &dest,
|
||||
ox::CRStringView path) noexcept {
|
||||
ox::StringViewCR path) noexcept {
|
||||
// copy
|
||||
oxTracef("pack.transformClaw", "path: {}", path);
|
||||
oxRequire(fileList, dest.ls(path));
|
||||
OX_REQUIRE(fileList, dest.ls(path));
|
||||
for (auto const&name : fileList) {
|
||||
auto const filePath = ox::sfmt("{}{}", path, name);
|
||||
oxRequire(stat, dest.stat(filePath));
|
||||
OX_REQUIRE(stat, dest.stat(filePath));
|
||||
if (stat.fileType == ox::FileType::Directory) {
|
||||
auto const dir = ox::sfmt("{}{}/", path, name);
|
||||
oxReturnError(transformClaw(ctx, ts, dest, dir));
|
||||
OX_RETURN_ERROR(transformClaw(manifest, ctx, ts, dest, dir));
|
||||
} else {
|
||||
auto const err = doTransformations(ctx, ts, dest, filePath);
|
||||
auto const err = doTransformations(manifest, ctx, ts, dest, filePath);
|
||||
if (err) {
|
||||
oxErrf("\033[31;1;1mCould not do transformations for {}:\n\t{}\n", filePath, toStr(err));
|
||||
return err;
|
||||
@@ -132,42 +158,53 @@ static ox::Error transformClaw(
|
||||
}
|
||||
|
||||
static ox::Error copy(
|
||||
Manifest &manifest,
|
||||
ox::FileSystem &src,
|
||||
ox::FileSystem &dest,
|
||||
ox::CRStringView path) noexcept {
|
||||
oxOutf("copying directory: {}\n", path);
|
||||
ox::StringViewCR path,
|
||||
ox::StringViewCR logPrefix = "") noexcept {
|
||||
oxOutf("{}copying directory: {}\n", logPrefix, path);
|
||||
auto const childLogPrefix = ox::sfmt("{}\t", logPrefix);
|
||||
// copy
|
||||
oxRequire(fileList, src.ls(path));
|
||||
OX_REQUIRE(fileList, src.ls(path));
|
||||
for (auto const&name : fileList) {
|
||||
auto currentFile = ox::sfmt("{}{}", path, name);
|
||||
auto const currentFile = ox::sfmt("{}{}", path, name);
|
||||
if (beginsWith(name, ".")) {
|
||||
continue;
|
||||
}
|
||||
oxOutf("reading {}\n", currentFile);
|
||||
oxRequire(stat, src.stat(currentFile));
|
||||
if (stat.fileType == ox::FileType::Directory) {
|
||||
oxReturnError(dest.mkdir(currentFile, true));
|
||||
oxReturnError(copy(src, dest, currentFile + '/'));
|
||||
OX_REQUIRE(srcStat, src.stat(currentFile));
|
||||
if (srcStat.fileType == ox::FileType::Directory) {
|
||||
OX_RETURN_ERROR(dest.mkdir(currentFile, true));
|
||||
OX_RETURN_ERROR(copy(manifest, src, dest, currentFile + '/', childLogPrefix));
|
||||
} else {
|
||||
// load file
|
||||
oxRequireM(buff, src.read(currentFile));
|
||||
oxOutf("{}copying file: {}...", childLogPrefix, currentFile);
|
||||
ox::StringView status = "failed";
|
||||
OX_DEFER [&status] {
|
||||
oxOutf(" {}\n", status);
|
||||
};
|
||||
OX_REQUIRE_M(buff, src.read(currentFile));
|
||||
// write file to dest
|
||||
oxOutf("writing {}\n", currentFile);
|
||||
oxReturnError(dest.write(currentFile, buff));
|
||||
OX_RETURN_ERROR(dest.write(currentFile, buff));
|
||||
status = "OK";
|
||||
OX_REQUIRE(dstStat, dest.stat(currentFile));
|
||||
manifest.files[currentFile] = {
|
||||
.inode = dstStat.inode,
|
||||
.type = ox::String{keel::readAssetTypeId(buff).or_value({})},
|
||||
};
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static ox::Error copyFS(ox::FileSystem &src, ox::FileSystem &dest) noexcept {
|
||||
oxReturnError(copy(src, dest, "/"));
|
||||
return {};
|
||||
}
|
||||
|
||||
ox::Error pack(keel::Context &ctx, ox::TypeStore &ts, ox::FileSystem &dest) noexcept {
|
||||
oxReturnError(copyFS(*ctx.rom, dest));
|
||||
ox::Error pack(
|
||||
Manifest &manifest,
|
||||
keel::Context &ctx,
|
||||
ox::TypeStore &ts,
|
||||
ox::FileSystem &dest) noexcept {
|
||||
OX_RETURN_ERROR(copy(manifest, *ctx.rom, dest, "/"));
|
||||
oxOut("Doing transforms\n");
|
||||
oxReturnError(transformClaw(ctx, ts, dest, "/"));
|
||||
OX_RETURN_ERROR(transformClaw(manifest, ctx, ts, dest, "/"));
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
@@ -2,35 +2,32 @@
|
||||
* Copyright 2016 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <ox/claw/read.hpp>
|
||||
|
||||
#include <keel/media.hpp>
|
||||
#include <keel/typeconv.hpp>
|
||||
|
||||
namespace keel {
|
||||
|
||||
[[nodiscard]]
|
||||
static ox::Result<BaseConverter const*> findConverter(
|
||||
ox::SpanView<BaseConverter const*> const&converters,
|
||||
ox::CRStringView srcTypeName,
|
||||
ox::StringViewCR srcTypeName,
|
||||
int srcTypeVersion,
|
||||
ox::CRStringView dstTypeName,
|
||||
ox::StringViewCR dstTypeName,
|
||||
int dstTypeVersion) noexcept {
|
||||
for (auto const&c : converters) {
|
||||
if (c->matches(srcTypeName, srcTypeVersion, dstTypeName, dstTypeVersion)) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
return OxError(1, "Could not find converter");
|
||||
return ox::Error(1, "Could not find converter");
|
||||
};
|
||||
|
||||
static ox::Result<ox::UPtr<Wrap>> convert(
|
||||
keel::Context &ctx,
|
||||
ox::SpanView<BaseConverter const*> const&converters,
|
||||
ox::Buffer const&srcBuffer,
|
||||
ox::CRStringView srcTypeName,
|
||||
ox::BufferView const&srcBuffer,
|
||||
ox::StringViewCR srcTypeName,
|
||||
int srcTypeVersion,
|
||||
ox::CRStringView dstTypeName,
|
||||
ox::StringViewCR dstTypeName,
|
||||
int dstTypeVersion) noexcept {
|
||||
// look for direct converter
|
||||
auto [c, err] = findConverter(
|
||||
@@ -50,15 +47,15 @@ static ox::Result<ox::UPtr<Wrap>> convert(
|
||||
return subConverter->convertPtrToPtr(ctx, *intermediate);
|
||||
}
|
||||
}
|
||||
return OxError(1, "Could not convert between types");
|
||||
return ox::Error(1, "Could not convert between types");
|
||||
}
|
||||
|
||||
ox::Result<ox::UPtr<Wrap>> convert(
|
||||
keel::Context &ctx,
|
||||
ox::Buffer const&srcBuffer,
|
||||
ox::CRStringView dstTypeName,
|
||||
ox::BufferView const&srcBuffer,
|
||||
ox::StringViewCR dstTypeName,
|
||||
int dstTypeVersion) noexcept {
|
||||
oxRequire(hdr, readAssetHeader(srcBuffer));
|
||||
OX_REQUIRE(hdr, readAssetHeader(srcBuffer));
|
||||
return convert(
|
||||
ctx,
|
||||
converters(ctx),
|
||||
|
||||
@@ -13,9 +13,9 @@ TypeStore::TypeStore(ox::FileSystem &fs, ox::StringView descPath) noexcept:
|
||||
|
||||
ox::Result<ox::UPtr<ox::DescriptorType>> TypeStore::loadDescriptor(ox::StringView const typeId) noexcept {
|
||||
auto const path = ox::sfmt("{}/{}", m_descPath, typeId);
|
||||
oxRequire(buff, m_fs.read(path));
|
||||
OX_REQUIRE(buff, m_fs.read(path));
|
||||
auto dt = ox::make_unique<ox::DescriptorType>();
|
||||
oxReturnError(ox::readClaw<ox::DescriptorType>(buff, *dt));
|
||||
OX_RETURN_ERROR(ox::readClaw<ox::DescriptorType>(buff, *dt));
|
||||
return dt;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,19 +15,20 @@ static std::map<ox::StringView, ox::Error(*)()> tests = {
|
||||
[]() -> ox::Error {
|
||||
constexpr ox::StringView uuidStr = "8d814442-f46e-4cc3-8edc-ca3c01cc86db";
|
||||
constexpr ox::StringView hdr = "K1;8d814442-f46e-4cc3-8edc-ca3c01cc86db;";
|
||||
oxRequire(uuid, ox::UUID::fromString(uuidStr));
|
||||
OX_REQUIRE(uuid, ox::UUID::fromString(uuidStr));
|
||||
ox::Array<char, hdr.len()> buff;
|
||||
ox::CharBuffWriter bw(buff);
|
||||
oxReturnError(keel::writeUuidHeader(bw, uuid));
|
||||
OX_RETURN_ERROR(keel::writeUuidHeader(bw, uuid));
|
||||
oxExpect(ox::StringView(buff.data(), buff.size()), hdr);
|
||||
return {};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
int main(int argc, const char **args) {
|
||||
int main(int argc, const char **argv) {
|
||||
int retval = -1;
|
||||
if (argc > 0) {
|
||||
auto const args = ox::Span{argv, static_cast<size_t>(argc)};
|
||||
auto testName = args[1];
|
||||
if (tests.find(testName) != tests.end()) {
|
||||
retval = static_cast<int>(tests[testName]());
|
||||
|
||||
@@ -12,6 +12,7 @@ add_library(
|
||||
)
|
||||
target_compile_definitions(
|
||||
StudioAppLib PUBLIC
|
||||
OLYMPIC_GUI_APP=1
|
||||
OLYMPIC_LOAD_STUDIO_MODULES=1
|
||||
OLYMPIC_APP_NAME="Studio"
|
||||
)
|
||||
|
||||
@@ -29,7 +29,7 @@ bool AboutPopup::isOpen() const noexcept {
|
||||
return m_stage == Stage::Open;
|
||||
}
|
||||
|
||||
void AboutPopup::draw(studio::StudioContext &sctx) noexcept {
|
||||
void AboutPopup::draw(StudioContext &sctx) noexcept {
|
||||
switch (m_stage) {
|
||||
case Stage::Closed:
|
||||
break;
|
||||
@@ -40,15 +40,14 @@ void AboutPopup::draw(studio::StudioContext &sctx) noexcept {
|
||||
case Stage::Open: {
|
||||
constexpr auto modalFlags =
|
||||
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize;
|
||||
ImGui::SetNextWindowSize(ImVec2(215, 90));
|
||||
studio::ig::centerNextWindow(sctx.tctx);
|
||||
ig::centerNextWindow(sctx.tctx);
|
||||
auto open = true;
|
||||
if (ImGui::BeginPopupModal("About", &open, modalFlags)) {
|
||||
ImGui::Text("%s", m_text.c_str());
|
||||
ImGui::NewLine();
|
||||
ImGui::Dummy(ImVec2(148.0f, 0.0f));
|
||||
ImGui::Dummy({148.0f, 0.0f});
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Close")) {
|
||||
if (ig::PushButton("Close")) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
open = false;
|
||||
}
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
|
||||
namespace studio {
|
||||
|
||||
ClawEditor::ClawEditor(ox::CRStringView path, ox::ModelObject obj) noexcept:
|
||||
Editor(path),
|
||||
m_obj(std::move(obj)) {
|
||||
ClawEditor::ClawEditor(StudioContext &sctx, ox::StringParam path):
|
||||
Editor(std::move(path)),
|
||||
m_obj(sctx.project->loadObj<ox::ModelObject>(itemPath()).unwrapThrow()) {
|
||||
}
|
||||
|
||||
void ClawEditor::draw(studio::StudioContext&) noexcept {
|
||||
void ClawEditor::draw(StudioContext&) noexcept {
|
||||
ImGui::BeginChild("PaletteEditor");
|
||||
static constexpr auto flags = ImGuiTableFlags_RowBg | ImGuiTableFlags_NoBordersInBody;
|
||||
if (ImGui::BeginTable("ObjTree", 3, flags)) {
|
||||
@@ -93,7 +93,7 @@ void ClawEditor::drawRow(ox::ModelValue const&value) noexcept {
|
||||
ImGui::Text("%s", val.c_str());
|
||||
}
|
||||
|
||||
void ClawEditor::drawVar(ObjPath &path, ox::CRStringView name, ox::ModelValue const&value) noexcept {
|
||||
void ClawEditor::drawVar(ObjPath &path, ox::StringViewCR name, ox::ModelValue const&value) noexcept {
|
||||
using Str = ox::BasicString<100>;
|
||||
path.push_back(name);
|
||||
if (value.type() == ox::ModelValue::Type::Object) {
|
||||
|
||||
@@ -11,19 +11,19 @@
|
||||
|
||||
namespace studio {
|
||||
|
||||
class ClawEditor: public studio::Editor {
|
||||
class ClawEditor: public Editor {
|
||||
private:
|
||||
using ObjPath = ox::Vector<ox::StringView, 8>;
|
||||
ox::ModelObject m_obj;
|
||||
public:
|
||||
ClawEditor(ox::CRStringView path, ox::ModelObject obj) noexcept;
|
||||
ClawEditor(StudioContext &sctx, ox::StringParam path);
|
||||
|
||||
void draw(studio::StudioContext&) noexcept final;
|
||||
void draw(StudioContext&) noexcept final;
|
||||
|
||||
private:
|
||||
static void drawRow(ox::ModelValue const&value) noexcept;
|
||||
|
||||
void drawVar(ObjPath &path, ox::CRStringView name, ox::ModelValue const&value) noexcept;
|
||||
void drawVar(ObjPath &path, ox::StringViewCR name, ox::ModelValue const&value) noexcept;
|
||||
|
||||
void drawTree(ObjPath &path, ox::ModelObject const&obj) noexcept;
|
||||
};
|
||||
|
||||
@@ -28,26 +28,19 @@ class StudioUIDrawer: public turbine::gl::Drawer {
|
||||
}
|
||||
};
|
||||
|
||||
static int updateHandler(turbine::Context &ctx) noexcept {
|
||||
auto sctx = turbine::applicationData<studio::StudioContext>(ctx);
|
||||
sctx->ui.update();
|
||||
return 16;
|
||||
}
|
||||
|
||||
static void keyEventHandler(turbine::Context &ctx, turbine::Key key, bool down) noexcept {
|
||||
auto sctx = turbine::applicationData<studio::StudioContext>(ctx);
|
||||
sctx->ui.handleKeyEvent(key, down);
|
||||
}
|
||||
|
||||
static ox::Error runApp(
|
||||
ox::CRStringView appName,
|
||||
ox::CRStringView projectDataDir,
|
||||
ox::StringViewCR appName,
|
||||
ox::StringViewCR projectDataDir,
|
||||
ox::UPtr<ox::FileSystem> &&fs) noexcept {
|
||||
oxRequireM(ctx, turbine::init(std::move(fs), appName));
|
||||
OX_REQUIRE_M(ctx, turbine::init(std::move(fs), appName));
|
||||
turbine::setWindowTitle(*ctx, keelCtx(*ctx).appName);
|
||||
turbine::setUpdateHandler(*ctx, updateHandler);
|
||||
turbine::setKeyEventHandler(*ctx, keyEventHandler);
|
||||
turbine::setConstantRefresh(*ctx, false);
|
||||
turbine::setRefreshWithin(*ctx, 0);
|
||||
StudioUI ui(*ctx, projectDataDir);
|
||||
StudioUIDrawer drawer(ui);
|
||||
turbine::gl::addDrawer(*ctx, &drawer);
|
||||
@@ -57,10 +50,9 @@ static ox::Error runApp(
|
||||
}
|
||||
|
||||
static ox::Error run(
|
||||
ox::CRStringView appName,
|
||||
ox::CRStringView projectDataDir,
|
||||
int,
|
||||
char const**) {
|
||||
ox::StringViewCR appName,
|
||||
ox::StringViewCR projectDataDir,
|
||||
ox::SpanView<const char*>) {
|
||||
// seed UUID generator
|
||||
auto const time = std::time(nullptr);
|
||||
ox::UUID::seedGenerator({
|
||||
@@ -68,22 +60,17 @@ static ox::Error run(
|
||||
static_cast<uint64_t>(time << 1)
|
||||
});
|
||||
// run app
|
||||
auto const err = runApp(appName, projectDataDir, ox::UPtr<ox::FileSystem>(nullptr));
|
||||
auto const err = runApp(appName, projectDataDir, ox::UPtr<ox::FileSystem>{});
|
||||
oxAssert(err, "Something went wrong...");
|
||||
return err;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace olympic {
|
||||
|
||||
ox::Error run(
|
||||
ox::StringView project,
|
||||
ox::StringView appName,
|
||||
ox::StringView projectDataDir,
|
||||
int argc,
|
||||
char const**argv) noexcept {
|
||||
return studio::run(ox::sfmt("{} {}", project, appName), projectDataDir, argc, argv);
|
||||
}
|
||||
|
||||
ox::SpanView<ox::CString> args) noexcept {
|
||||
return studio::run(ox::sfmt("{} {}", project, appName), projectDataDir, args);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
namespace studio {
|
||||
|
||||
NewMenu::NewMenu() noexcept {
|
||||
setTitle(ox::String("New Item"));
|
||||
setTitle("New Item");
|
||||
setSize({230, 140});
|
||||
}
|
||||
|
||||
@@ -63,12 +63,14 @@ void NewMenu::addItemMaker(ox::UniquePtr<studio::ItemMaker> &&im) noexcept {
|
||||
|
||||
void NewMenu::drawNewItemType(studio::StudioContext &sctx) noexcept {
|
||||
drawWindow(sctx.tctx, &m_open, [this] {
|
||||
auto items = ox_malloca(m_types.size() * sizeof(char const*), char const*, nullptr);
|
||||
auto const allocSz = m_types.size() * sizeof(char const*);
|
||||
auto mem = ox_malloca(allocSz, char const*, nullptr);
|
||||
auto items = ox::Span{mem.get(), allocSz};
|
||||
for (auto i = 0u; auto const&im : m_types) {
|
||||
items.get()[i] = im->typeName.c_str();
|
||||
items[i] = im->typeName.c_str();
|
||||
++i;
|
||||
}
|
||||
ImGui::ListBox("Item Type", &m_selectedType, items.get(), static_cast<int>(m_types.size()));
|
||||
ImGui::ListBox("Item Type", &m_selectedType, items.data(), static_cast<int>(m_types.size()));
|
||||
drawFirstPageButtons();
|
||||
});
|
||||
}
|
||||
@@ -77,7 +79,7 @@ void NewMenu::drawNewItemName(studio::StudioContext &sctx) noexcept {
|
||||
drawWindow(sctx.tctx, &m_open, [this, &sctx] {
|
||||
auto const typeIdx = static_cast<std::size_t>(m_selectedType);
|
||||
if (typeIdx < m_types.size()) {
|
||||
ImGui::InputText("Name", m_itemName.data(), m_itemName.cap());
|
||||
ig::InputText("Name", m_itemName);
|
||||
}
|
||||
drawLastPageButtons(sctx);
|
||||
});
|
||||
@@ -116,11 +118,12 @@ void NewMenu::drawLastPageButtons(studio::StudioContext &sctx) noexcept {
|
||||
|
||||
void NewMenu::finish(studio::StudioContext &sctx) noexcept {
|
||||
if (m_itemName.len() == 0) {
|
||||
oxLogError(ox::Error(1, "New file error: no file name"));
|
||||
return;
|
||||
}
|
||||
auto const&typeMaker = *m_types[static_cast<std::size_t>(m_selectedType)];
|
||||
if (sctx.project->exists(typeMaker.itemPath(m_itemName))) {
|
||||
oxLogError(OxError(1, "New file error: File already exists"));
|
||||
oxLogError(ox::Error(1, "New file error: file already exists"));
|
||||
return;
|
||||
}
|
||||
auto const [path, err] = typeMaker.write(sctx, m_itemName);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user