Merge branch 'master' into stable
Some checks failed
Build / build (push) Has been cancelled

This commit is contained in:
2024-12-21 18:58:40 -06:00
658 changed files with 67679 additions and 31425 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -21,7 +21,7 @@ ox::Error initGfx(Context &ctx, InitParams const&) noexcept;
ox::Result<ContextUPtr> init(turbine::Context &tctx, InitParams const&params) noexcept {
auto ctx = ox::make_unique<Context>(tctx);
oxReturnError(initGfx(*ctx, params));
OX_RETURN_ERROR(initGfx(*ctx, params));
return ContextUPtr(std::move(ctx));
}

View File

@@ -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 &sectionIdx, int tileIdx) noexcept {
if (setEntry.sections.size() <= sectionIdx) {
return false;
}
auto &section = 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, &sectionIdx, 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, &sectionIdx](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 = &REG_BG0CTL; bgCtl <= &REG_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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -25,7 +25,7 @@ Context::~Context() noexcept {
ox::Result<ContextUPtr> init(turbine::Context &tctx, InitParams const&params) noexcept {
auto ctx = ox::make_unique<Context>(tctx, params);
oxReturnError(initGfx(*ctx, params));
OX_RETURN_ERROR(initGfx(*ctx, params));
return ContextUPtr(ctx.release());
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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 {};
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -12,6 +12,7 @@ add_library(
)
target_compile_definitions(
StudioAppLib PUBLIC
OLYMPIC_GUI_APP=1
OLYMPIC_LOAD_STUDIO_MODULES=1
OLYMPIC_APP_NAME="Studio"
)

View File

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

View File

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

View File

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

View File

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

View File

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