Squashed 'deps/nostalgia/' changes from 671b8eda..fae1e73e

fae1e73e [nostalgia/gfx] Make getSubSheet check root subsheet name
51f2905c [nostalgia/gfx/studio] Make TileSheetEditor use export-tilesheet functions for export, fix exporting Root subsheet
0c866d1b [studio,nostalgia/gfx] Add system for adding sub-commands in Modules, add export-tilesheet command
fdf39d1a [ox/std] Add Result::transformError
a523a75e [ox/std] Add missing include to StringParam
cdaa64ed [ox/clargs] Fix arg parsing for first '-'
37b5fcc0 [teagba] Put parentheses around all registers
f5f2c3be [studio/applib] Make Studio remove files unable to be opened from open file list in config
f6ef2b5a [turbine,nostalgia] Make memory regions cast to bounded ox::Arrays
bf958a4a [teagba] Make memory regions cast to bounded ox::Arrays
6a70e478 [nostalgia/gfx] Cleanup

git-subtree-dir: deps/nostalgia
git-subtree-split: fae1e73e54a420d4b93af67e03144d5442825a11
This commit is contained in:
2025-07-24 01:58:27 -05:00
parent b0726568df
commit dceeaaa930
23 changed files with 455 additions and 178 deletions

View File

@ -15,7 +15,7 @@ ClArgs::ClArgs(int argc, const char **args) noexcept: ClArgs({args, static_cast<
ClArgs::ClArgs(ox::SpanView<const char*> args) noexcept {
for (auto i = 0u; i < args.size(); ++i) {
auto arg = StringView(args[i]);
auto arg = StringView{args[i]};
if (arg[0] == '-') {
while (arg[0] == '-' && arg.len()) {
arg = substr(arg, 1);
@ -23,8 +23,8 @@ ClArgs::ClArgs(ox::SpanView<const char*> args) noexcept {
m_bools[arg] = true;
// parse additional arguments
if (i < args.size() && args[i + 1]) {
auto val = String(args[i + 1]);
if (val.len() && val[i] != '-') {
auto const val = StringView{args[i + 1]};
if (val.len() && val[0] != '-') {
if (val == "false") {
m_bools[arg] = false;
}
@ -40,17 +40,17 @@ ClArgs::ClArgs(ox::SpanView<const char*> args) noexcept {
}
bool ClArgs::getBool(ox::StringViewCR arg, bool defaultValue) const noexcept {
auto [value, err] = m_ints.at(arg);
auto const [value, err] = m_ints.at(arg);
return !err ? *value : defaultValue;
}
String ClArgs::getString(ox::StringViewCR arg, ox::StringView defaultValue) const noexcept {
auto [value, err] = m_strings.at(arg);
auto const [value, err] = m_strings.at(arg);
return !err ? ox::String(*value) : ox::String(defaultValue);
}
int ClArgs::getInt(ox::StringViewCR arg, int defaultValue) const noexcept {
auto [value, err] = m_ints.at(arg);
auto const [value, err] = m_ints.at(arg);
return !err ? *value : defaultValue;
}

View File

@ -307,6 +307,13 @@ struct [[nodiscard]] Result {
return std::move(value);
}
constexpr Result transformError(ErrorCode const ec, CString const msg) && {
if (error) {
error = Error{ec, msg};
}
return *this;
}
};
namespace detail {

View File

@ -8,6 +8,7 @@
#pragma once
#include "cstringview.hpp"
#include "string.hpp"
namespace ox {

View File

@ -5,103 +5,101 @@
#pragma once
#include <ox/std/array.hpp>
#include <ox/std/types.hpp>
#include <ox/std/units.hpp>
/////////////////////////////////////////////////////////////////
// Interrupt Handler
using interrupt_handler = void (*)();
#define REG_ISR *reinterpret_cast<interrupt_handler*>(0x0300'7FFC)
#define REG_IE *reinterpret_cast<volatile uint16_t*>(0x0400'0200)
#define REG_IF *reinterpret_cast<volatile uint16_t*>(0x0400'0202)
#define REG_IME *reinterpret_cast<volatile uint16_t*>(0x0400'0208)
using InterruptHandler = void(*)();
#define REG_ISR (*reinterpret_cast<InterruptHandler*>(0x0300'7FFC))
#define REG_IE (*reinterpret_cast<volatile uint16_t*>(0x0400'0200))
#define REG_IF (*reinterpret_cast<volatile uint16_t*>(0x0400'0202))
#define REG_IME (*reinterpret_cast<volatile uint16_t*>(0x0400'0208))
/////////////////////////////////////////////////////////////////
// Display Registers
#define REG_DISPCTL *reinterpret_cast<volatile uint32_t*>(0x0400'0000)
#define REG_DISPSTAT *reinterpret_cast<volatile uint32_t*>(0x0400'0004)
#define REG_VCOUNT *reinterpret_cast<volatile uint32_t*>(0x0400'0006)
#define REG_DISPCTL (*reinterpret_cast<volatile uint32_t*>(0x0400'0000))
#define REG_DISPSTAT (*reinterpret_cast<volatile uint32_t*>(0x0400'0004))
#define REG_VCOUNT (*reinterpret_cast<volatile uint32_t*>(0x0400'0006))
/////////////////////////////////////////////////////////////////
// Timers
#define REG_TIMER0 *reinterpret_cast<volatile uint16_t*>(0x0400'0100)
#define REG_TIMER0CTL *reinterpret_cast<volatile uint16_t*>(0x0400'0102)
#define REG_TIMER0 (*reinterpret_cast<volatile uint16_t*>(0x0400'0100))
#define REG_TIMER0CTL (*reinterpret_cast<volatile uint16_t*>(0x0400'0102))
#define REG_TIMER1 *reinterpret_cast<volatile uint16_t*>(0x0400'0104)
#define REG_TIMER1CTL *reinterpret_cast<volatile uint16_t*>(0x0400'0106)
#define REG_TIMER1 (*reinterpret_cast<volatile uint16_t*>(0x0400'0104))
#define REG_TIMER1CTL (*reinterpret_cast<volatile uint16_t*>(0x0400'0106))
#define REG_TIMER2 *reinterpret_cast<volatile uint16_t*>(0x0400'0108)
#define REG_TIMER2CTL *reinterpret_cast<volatile uint16_t*>(0x0400'010a)
#define REG_TIMER2 (*reinterpret_cast<volatile uint16_t*>(0x0400'0108))
#define REG_TIMER2CTL (*reinterpret_cast<volatile uint16_t*>(0x0400'010a))
#define REG_TIMER3 *reinterpret_cast<volatile uint16_t*>(0x0400'010c)
#define REG_TIMER3CTL *reinterpret_cast<volatile uint16_t*>(0x0400'010e)
#define REG_TIMER3 (*reinterpret_cast<volatile uint16_t*>(0x0400'010c))
#define REG_TIMER3CTL (*reinterpret_cast<volatile uint16_t*>(0x0400'010e))
/////////////////////////////////////////////////////////////////
// background registers
// background control registers
using BgCtl = uint16_t;
#define REG_BG0CTL *reinterpret_cast<volatile BgCtl*>(0x0400'0008)
#define REG_BG1CTL *reinterpret_cast<volatile BgCtl*>(0x0400'000a)
#define REG_BG2CTL *reinterpret_cast<volatile BgCtl*>(0x0400'000c)
#define REG_BG3CTL *reinterpret_cast<volatile BgCtl*>(0x0400'000e)
#define REG_BG0CTL (*reinterpret_cast<volatile BgCtl*>(0x0400'0008))
#define REG_BG1CTL (*reinterpret_cast<volatile BgCtl*>(0x0400'000a))
#define REG_BG2CTL (*reinterpret_cast<volatile BgCtl*>(0x0400'000c))
#define REG_BG3CTL (*reinterpret_cast<volatile BgCtl*>(0x0400'000e))
[[nodiscard]]
inline volatile BgCtl &regBgCtl(uintptr_t bgIdx) noexcept {
inline volatile BgCtl &regBgCtl(uintptr_t const bgIdx) noexcept {
return *reinterpret_cast<volatile BgCtl*>(0x0400'0008 + 2 * bgIdx);
}
// background horizontal scrolling registers
#define REG_BG0HOFS *reinterpret_cast<volatile uint32_t*>(0x0400'0010)
#define REG_BG1HOFS *reinterpret_cast<volatile uint32_t*>(0x0400'0014)
#define REG_BG2HOFS *reinterpret_cast<volatile uint32_t*>(0x0400'0018)
#define REG_BG3HOFS *reinterpret_cast<volatile uint32_t*>(0x0400'001c)
#define REG_BG0HOFS (*reinterpret_cast<volatile uint32_t*>(0x0400'0010))
#define REG_BG1HOFS (*reinterpret_cast<volatile uint32_t*>(0x0400'0014))
#define REG_BG2HOFS (*reinterpret_cast<volatile uint32_t*>(0x0400'0018))
#define REG_BG3HOFS (*reinterpret_cast<volatile uint32_t*>(0x0400'001c))
[[nodiscard]]
inline volatile uint32_t &regBgHofs(auto bgIdx) noexcept {
volatile uint32_t &regBgHofs(auto const bgIdx) noexcept {
return *reinterpret_cast<volatile uint32_t*>(0x0400'0010 + 4 * bgIdx);
}
// background vertical scrolling registers
#define REG_BG0VOFS *reinterpret_cast<volatile uint32_t*>(0x0400'0012)
#define REG_BG1VOFS *reinterpret_cast<volatile uint32_t*>(0x0400'0016)
#define REG_BG2VOFS *reinterpret_cast<volatile uint32_t*>(0x0400'001a)
#define REG_BG3VOFS *reinterpret_cast<volatile uint32_t*>(0x0400'001e)
#define REG_BG0VOFS (*reinterpret_cast<volatile uint32_t*>(0x0400'0012))
#define REG_BG1VOFS (*reinterpret_cast<volatile uint32_t*>(0x0400'0016))
#define REG_BG2VOFS (*reinterpret_cast<volatile uint32_t*>(0x0400'001a))
#define REG_BG3VOFS (*reinterpret_cast<volatile uint32_t*>(0x0400'001e))
[[nodiscard]]
inline volatile uint32_t &regBgVofs(auto bgIdx) noexcept {
volatile uint32_t &regBgVofs(auto const bgIdx) noexcept {
return *reinterpret_cast<volatile uint32_t*>(0x0400'0012 + 4 * bgIdx);
}
/////////////////////////////////////////////////////////////////
// User Input
#define REG_GAMEPAD *reinterpret_cast<volatile uint16_t*>(0x0400'0130)
#define REG_GAMEPAD (*reinterpret_cast<volatile uint16_t*>(0x0400'0130))
/////////////////////////////////////////////////////////////////
// Memory Addresses
#define MEM_EWRAM_BEGIN reinterpret_cast<uint8_t*>(0x0200'0000)
#define MEM_EWRAM_END reinterpret_cast<uint8_t*>(0x0203'FFFF)
#define MEM_EWRAM (*reinterpret_cast<ox::Array<uint16_t, 0x0203'FFFF - 0x0200'0000>*>(0x0200'0000))
#define MEM_IWRAM_BEGIN reinterpret_cast<uint8_t*>(0x0300'0000)
#define MEM_IWRAM_END reinterpret_cast<uint8_t*>(0x0300'7FFF)
#define MEM_IWRAM (*reinterpret_cast<ox::Array<uint8_t, 0x0300'7FFF - 0x0300'0000>*>(0x0300'0000))
#define REG_BLNDCTL *reinterpret_cast<uint16_t*>(0x0400'0050)
#define REG_BLNDCTL (*reinterpret_cast<uint16_t*>(0x0400'0050))
#define MEM_BG_PALETTE reinterpret_cast<uint16_t*>(0x0500'0000)
#define MEM_SPRITE_PALETTE reinterpret_cast<uint16_t*>(0x0500'0200)
using Palette = ox::Array<uint16_t, 128>;
#define MEM_BG_PALETTE (*reinterpret_cast<::Palette*>(0x0500'0000))
#define MEM_SPRITE_PALETTE (*reinterpret_cast<::Palette*>(0x0500'0200))
using BgMapTile = ox::Array<uint16_t, 8192>;
#define MEM_BG_TILES reinterpret_cast<BgMapTile*>(0x0600'0000)
#define MEM_BG_MAP reinterpret_cast<BgMapTile*>(0x0600'e000)
#define MEM_BG_TILES (*reinterpret_cast<ox::Array<BgMapTile, 4>*>(0x0600'0000))
#define MEM_BG_MAP (*reinterpret_cast<ox::Array<BgMapTile, 4>*>(0x0600'e000))
#define MEM_SPRITE_TILES reinterpret_cast<uint16_t*>(0x0601'0000)
#define MEM_OAM reinterpret_cast<uint64_t*>(0x0700'0000)
#define MEM_SPRITE_TILES (*reinterpret_cast<ox::Array<uint16_t, 32 * ox::units::KB>*>(0x0601'0000))
#define MEM_OAM (*reinterpret_cast<ox::Array<uint64_t, 64>*>(0x0700'0000))
#define MEM_ROM reinterpret_cast<char*>(0x0800'0000)
#define MEM_ROM (*reinterpret_cast<ox::Array<char, 32 * ox::units::MB>*>(0x0700'0000))
#define MEM_SRAM reinterpret_cast<char*>(0x0e00'0000)
#define MEM_SRAM_SIZE 65535
#define MEM_SRAM (*reinterpret_cast<ox::Array<char, 64 * ox::units::KB>*>(0x0e00'0000))

View File

@ -428,6 +428,14 @@ TileSheet::SubSheetIdx validateSubSheetIdx(TileSheet const&ts, TileSheet::SubShe
ox::Result<TileSheet::SubSheetIdx> getSubSheetIdx(TileSheet const &ts, SubSheetId pId) noexcept;
ox::Result<TileSheet::SubSheet*> getSubSheet(
ox::SpanView<ox::StringView> const &idx,
TileSheet &ts) noexcept;
ox::Result<TileSheet::SubSheet const*> getSubSheet(
ox::SpanView<ox::StringView> const &idx,
TileSheet const &ts) noexcept;
[[nodiscard]]
TileSheet::SubSheet &getSubSheet(
ox::SpanView<uint32_t> const&idx,

View File

@ -35,11 +35,6 @@ class Context {
Context(Context const &&other) noexcept = delete;
virtual ~Context() noexcept = default;
[[nodiscard]]
ox::MemFS const &rom() const noexcept {
return static_cast<ox::MemFS const&>(*turbine::rom(turbineCtx));
}
};
void safeDelete(Context *ctx) noexcept {
@ -81,7 +76,7 @@ ox::Error loadBgPalette(
if (palette.pages.empty()) {
return {};
}
auto const paletteMem = MEM_BG_PALETTE + palBank * 16;
auto const paletteMem = ox::Span{MEM_BG_PALETTE} + palBank * 16;
for (auto i = 0u; i < colorCnt(palette, page); ++i) {
paletteMem[i] = color(palette, page, i);
}
@ -95,9 +90,8 @@ ox::Error loadSpritePalette(
if (palette.pages.empty()) {
return {};
}
auto const paletteMem = MEM_SPRITE_PALETTE;
for (auto i = 0u; i < colorCnt(palette, page); ++i) {
paletteMem[i] = color(palette, page, i);
MEM_SPRITE_PALETTE[i] = color(palette, page, i);
}
return {};
}
@ -240,7 +234,7 @@ ox::Error loadSpriteTileSheet(
Context &ctx,
TileSheetSet const &set) noexcept {
auto const bpp = static_cast<unsigned>(set.bpp);
OX_RETURN_ERROR(loadTileSheetSet(ctx, {MEM_SPRITE_TILES, 32 * ox::units::KB}, set));
OX_RETURN_ERROR(loadTileSheetSet(ctx, MEM_SPRITE_TILES, set));
setSpritesBpp(bpp);
return {};
}
@ -349,9 +343,9 @@ void panic(char const *file, int line, char const *panicMsg, ox::Error const &er
using namespace nostalgia::gfx;
// reset heap to make sure we have enough memory to allocate context data
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
auto const heapBegin = reinterpret_cast<char*>(MEM_EWRAM_BEGIN);
auto const heapSz = (MEM_EWRAM_END - MEM_EWRAM_BEGIN) / 2;
auto const heapEnd = reinterpret_cast<char*>(MEM_EWRAM_BEGIN + heapSz);
auto const heapBegin = reinterpret_cast<char*>(MEM_EWRAM.data());
auto const heapSz = MEM_EWRAM.size() / 2;
auto const heapEnd = reinterpret_cast<char*>(MEM_EWRAM.data() + heapSz);
ox::heapmgr::initHeap(heapBegin, heapEnd);
OX_ALLOW_UNSAFE_BUFFERS_END
auto tctx = turbine::init(keel::loadRomFs("").unwrap(), "Nostalgia").unwrap();

View File

@ -26,3 +26,4 @@ install(
add_subdirectory(paletteeditor)
add_subdirectory(tilesheeteditor)
add_subdirectory(subcommands)

View File

@ -8,10 +8,25 @@
#include "paletteeditor/paletteeditor-imgui.hpp"
#include "tilesheeteditor/tilesheeteditor-imgui.hpp"
#include "subcommands/export-tilesheet/export-tilesheet.hpp"
namespace nostalgia::gfx {
static class: public studio::Module {
static struct: studio::Module {
ox::String id() const noexcept final {
return ox::String{"net.drinkingtea.nostalgia.gfx"};
}
ox::Vector<studio::Command> commands() const final {
return {
{
"export-tilesheet",
cmdExportTilesheet,
}
};
}
ox::Vector<studio::EditorMaker> editors(studio::Context &ctx) const noexcept final {
return {
studio::editorMaker<TileSheetEditorImGui>(ctx, {FileExt_ng, FileExt_nts}),
@ -28,6 +43,7 @@ static class: public studio::Module {
}, ox::ClawFormat::Organic));
return out;
}
} const mod;
studio::Module const *studioModule() noexcept {

View File

@ -0,0 +1,10 @@
target_sources(
NostalgiaGfx-Studio PRIVATE
export-tilesheet/export-tilesheet.cpp
)
target_link_libraries(
NostalgiaGfx-Studio PUBLIC
OxClArgs
)

View File

@ -0,0 +1,158 @@
/*
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <lodepng.h>
#include <ox/clargs/clargs.hpp>
#include <ox/std/trace.hpp>
#include <studio/project.hpp>
#include <nostalgia/gfx/palette.hpp>
#include <nostalgia/gfx/tilesheet.hpp>
#include "export-tilesheet.hpp"
#include "nostalgia/gfx/tilesheet.hpp"
#include "studio/context.hpp"
namespace nostalgia::gfx {
static ox::Vector<uint32_t> normalizePixelSizes(
ox::Vector<uint8_t> const &inPixels) noexcept {
ox::Vector<uint32_t> outPixels;
outPixels.reserve(inPixels.size());
outPixels.resize(inPixels.size());
for (size_t i{}; i < inPixels.size(); ++i) {
outPixels[i] = inPixels[i];
}
return outPixels;
}
static ox::Vector<uint32_t> normalizePixelArrangement(
ox::Vector<uint32_t> const &inPixels,
int const cols,
int const scale) {
auto const scalePt = ox::Point{scale, scale};
auto const width = cols * TileWidth;
auto const height = static_cast<int>(inPixels.size()) / width;
auto const dstWidth = width * scale;
ox::Vector<uint32_t> outPixels(static_cast<size_t>((width * scale) * (height * scale)));
for (size_t dstIdx{}; dstIdx < outPixels.size(); ++dstIdx) {
auto const dstPt = ox::Point{
static_cast<int>(dstIdx) % dstWidth,
static_cast<int>(dstIdx) / dstWidth};
auto const srcPt = dstPt / scalePt;
auto const srcIdx = ptToIdx(srcPt, cols);
outPixels[dstIdx] = inPixels[srcIdx];
}
return outPixels;
}
static ox::Error toPngFile(
ox::CStringViewCR path,
ox::Vector<uint32_t> &&pixels,
Palette const &pal,
size_t const page,
unsigned const width,
unsigned const height) noexcept {
for (auto &c : pixels) {
c = color32(color(pal, page, c)) | static_cast<Color32>(0XFF << 24);
}
constexpr auto fmt = LCT_RGBA;
return ox::Error(static_cast<ox::ErrorCode>(
lodepng_encode_file(
path.c_str(),
reinterpret_cast<uint8_t const*>(pixels.data()),
width,
height,
fmt,
8)));
}
ox::Error exportSubsheetToPng(
TileSheet::SubSheet const &s,
Palette const &pal,
size_t const palPage,
ox::CStringViewCR dstPath,
int const scale) noexcept {
// subsheet to png
auto const width = s.columns * TileWidth;
auto const height = s.rows * TileHeight;
auto const err = toPngFile(
dstPath,
normalizePixelArrangement(
normalizePixelSizes(s.pixels),
s.columns,
scale),
pal,
palPage,
static_cast<unsigned>(width * scale),
static_cast<unsigned>(height * scale));
if (err) {
oxErrorf("TileSheet export failed: {}", toStr(err));
return ox::Error{7, "TileSheet export failed"};
}
return {};
}
ox::Error exportSubsheetToPng(
TileSheet const &src,
Palette const &pal,
ox::StringViewCR subsheetPath,
size_t const palPage,
ox::CStringViewCR dstPath,
int const scale) noexcept {
// subsheet to png
auto const [s, ssErr] = getSubSheet(
ox::split(subsheetPath, '.'), src);
if (ssErr) {
return ox::Error{6, "failed to find SubSheet"};
}
return exportSubsheetToPng(
*s,
pal,
palPage,
dstPath,
scale);
}
ox::Error cmdExportTilesheet(studio::Project &project, ox::SpanView<ox::CString> const args) noexcept {
// parse args
ox::ClArgs const clargs{args};
bool showUsage = true;
OX_DEFER [&showUsage] {
if (showUsage) {
oxErr("usage: export-tilesheet "
"-src-path <path> "
"-dst-path <path> "
"[-pal-path <path>] "
"[-subsheet-path <path>] "
"[-scale <int>]\n");
}
};
OX_REQUIRE(srcPath, clargs.getString("src-path").transformError(1, "no src path specified"));
OX_REQUIRE(dstPath, clargs.getString("dst-path").transformError(2, "no dst path specified"));
auto const palPath = clargs.getString("pal-path", "");
auto const subsheetPath = clargs.getString("subsheet-path").or_value(ox::String{"Root"});
auto const palPage = static_cast<size_t>(clargs.getInt("pal-page", 0));
auto const scale = clargs.getInt("scale", 1);
showUsage = false;
// load objects
auto &kctx = project.kctx();
OX_REQUIRE(ts, keel::readObj<TileSheet>(kctx, srcPath).transformError(4, "could not load TileSheet"));
OX_REQUIRE(pal, keel::readObj<Palette>(kctx, palPath.len() ? palPath : ts->defaultPalette)
.transformError(5, "could not load Palette"));
// export to the destination file
return exportSubsheetToPng(
*ts,
*pal,
subsheetPath,
palPage,
dstPath,
scale);
}
}

View File

@ -0,0 +1,28 @@
/*
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <ox/std/error.hpp>
namespace nostalgia::gfx {
ox::Error exportSubsheetToPng(
TileSheet::SubSheet const &s,
Palette const &pal,
size_t palPage,
ox::CStringViewCR dstPath,
int scale) noexcept;
ox::Error exportSubsheetToPng(
TileSheet const &src,
Palette const &pal,
ox::StringViewCR subsheetPath,
size_t palPage,
ox::CStringViewCR dstPath,
int scale) noexcept;
ox::Error cmdExportTilesheet(studio::Project& project, ox::SpanView<ox::CString> args) noexcept;
}

View File

@ -11,6 +11,7 @@
#include <nostalgia/gfx/studio.hpp>
#include "../subcommands/export-tilesheet/export-tilesheet.hpp"
#include "tilesheeteditor-imgui.hpp"
namespace nostalgia::gfx {
@ -37,58 +38,6 @@ 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) noexcept {
ox::Vector<uint32_t> outPixels;
outPixels.reserve(inPixels.size());
outPixels.resize(inPixels.size());
for (std::size_t i = 0; i < inPixels.size(); ++i) {
outPixels[i] = inPixels[i];
}
return outPixels;
}
static ox::Vector<uint32_t> normalizePixelArrangement(
ox::Vector<uint32_t> const &inPixels,
int const cols,
int const scale) {
auto const scalePt = ox::Point{scale, scale};
auto const width = cols * TileWidth;
auto const height = static_cast<int>(inPixels.size()) / width;
auto const dstWidth = width * scale;
ox::Vector<uint32_t> outPixels(static_cast<size_t>((width * scale) * (height * scale)));
for (std::size_t dstIdx = 0; dstIdx < outPixels.size(); ++dstIdx) {
auto const dstPt = ox::Point{
static_cast<int>(dstIdx) % dstWidth,
static_cast<int>(dstIdx) / dstWidth};
auto const srcPt = dstPt / scalePt;
auto const srcIdx = ptToIdx(srcPt, cols);
outPixels[dstIdx] = inPixels[srcIdx];
}
return outPixels;
}
static ox::Error toPngFile(
ox::CStringViewCR path,
ox::Vector<uint32_t> &&pixels,
Palette const &pal,
size_t const page,
unsigned const width,
unsigned const height) noexcept {
for (auto &c : pixels) {
c = color32(color(pal, page, c)) | static_cast<Color32>(0XFF << 24);
}
constexpr auto fmt = LCT_RGBA;
return ox::Error(static_cast<ox::ErrorCode>(
lodepng_encode_file(
path.c_str(),
reinterpret_cast<uint8_t const*>(pixels.data()),
width,
height,
fmt,
8)));
}
TileSheetEditorImGui::TileSheetEditorImGui(studio::Context &sctx, ox::StringParam path):
Editor{sctx, std::move(path)},
m_sctx{sctx},
@ -421,21 +370,7 @@ ox::Error TileSheetEditorImGui::exportSubsheetToPng(int const scale) const noexc
// subsheet to png
auto const &s = m_model.activeSubSheet();
auto const &pal = m_model.pal();
auto const width = s.columns * TileWidth;
auto const height = s.rows * TileHeight;
auto pixels = normalizePixelSizes(s.pixels);
pixels = normalizePixelArrangement(pixels, s.columns, scale);
auto const err = toPngFile(
path,
std::move(pixels),
pal,
m_model.palettePage(),
static_cast<unsigned>(width * scale),
static_cast<unsigned>(height * scale));
if (err) {
oxErrorf("TileSheet export failed: {}", toStr(err));
}
return err;
return gfx::exportSubsheetToPng(s, pal, m_model.palettePage(), path, scale);
}
void TileSheetEditorImGui::drawTileSheet(ox::Vec2 const &fbSize) noexcept {

View File

@ -211,6 +211,47 @@ ox::Result<TileSheet::SubSheetIdx> getSubSheetIdx(TileSheet const &ts, SubSheetI
return out;
}
template<typename SubSheet>
static ox::Result<SubSheet*> getSubSheet(
ox::SpanView<ox::StringView> const &idx,
std::size_t const idxIt,
SubSheet &pSubSheet) noexcept {
if (idxIt == idx.size()) {
return &pSubSheet;
}
auto const &currentIdx = idx[idxIt];
auto const next = ox::find_if(
pSubSheet.subsheets.begin(),
pSubSheet.subsheets.end(),
[&currentIdx](TileSheet::SubSheet const &ss) {
return ss.name == currentIdx;
});
if (next == pSubSheet.subsheets.end()) {
return ox::Error{1, "SubSheet not found"};
}
return getSubSheet(idx, idxIt + 1, *next);
}
ox::Result<TileSheet::SubSheet const*> getSubSheet(
ox::SpanView<ox::StringView> const &idx,
TileSheet const &ts) noexcept {
if (!idx.empty() && idx[0] == ts.subsheet.name) {
return getSubSheet<TileSheet::SubSheet const>(idx, 1, ts.subsheet);
}
return ox::Error{1, "SubSheet not found"};
}
ox::Result<TileSheet::SubSheet*> getSubSheet(
ox::SpanView<ox::StringView> const &idx,
TileSheet &ts) noexcept {
if (!idx.empty() && idx[0] == ts.subsheet.name) {
return getSubSheet<TileSheet::SubSheet>(idx, 1, ts.subsheet);
}
return ox::Error{1, "SubSheet not found"};
}
#if defined(__GNUC__) && __GNUC__ >= 13
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdangling-reference"

View File

@ -10,6 +10,10 @@ namespace nostalgia::sound {
static struct: studio::Module {
ox::String id() const noexcept final {
return ox::String{"net.drinkingtea.nostalgia.sound"};
}
ox::Vector<studio::EditorMaker> editors(studio::Context&) const noexcept final {
return {
};
@ -22,7 +26,7 @@ static struct: studio::Module {
} const mod;
const studio::Module *studioModule() noexcept {
studio::Module const *studioModule() noexcept {
return &mod;
}

View File

@ -10,6 +10,9 @@
namespace studio {
[[nodiscard]]
ox::Vector<Module const*> const &modules() noexcept;
void registerModule(Module const*) noexcept;
}

View File

@ -2,15 +2,18 @@
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <complex>
#include <ctime>
#include <ox/logconn/logconn.hpp>
#include <ox/std/trace.hpp>
#include <ox/std/uuid.hpp>
#include <keel/media.hpp>
#include <keel/keel.hpp>
#include <turbine/turbine.hpp>
#include <studio/context.hpp>
#include <studioapp/studioapp.hpp>
#include "studioui.hpp"
namespace studio {
@ -40,7 +43,7 @@ static void mouseButtonEventHandler(turbine::Context &ctx, int const btn, bool c
[[nodiscard]]
ox::Vector<ox::SpanView<uint8_t>> WindowIcons() noexcept;
static ox::Error runApp(
static ox::Error runStudio(
ox::StringViewCR appName,
ox::StringViewCR projectDataDir,
ox::UPtr<ox::FileSystem> &&fs) noexcept {
@ -61,18 +64,47 @@ static ox::Error runApp(
static ox::Error run(
ox::StringViewCR appName,
ox::StringViewCR projectDataDir,
ox::SpanView<ox::CString>) {
ox::SpanView<ox::CString> const &args) {
// seed UUID generator
auto const time = std::time(nullptr);
ox::UUID::seedGenerator({
static_cast<uint64_t>(time),
static_cast<uint64_t>(time << 1)
});
if (args.size() > 1 && ox::StringView{args[1]} == "cmd") {
if (args.size() < 5) {
return ox::Error{1, "insufficient arguments for sub-command"};
}
auto constexpr numCmdArgs = 5;
ox::StringView const projectDir = args[2];
ox::StringView const moduleId = args[3];
ox::StringView const subCmd = args[4];
for (auto const m : modules()) {
if (m->id() == moduleId) {
for (auto const &c : m->commands()) {
if (c.name == subCmd) {
auto kctx = keel::init(
ox::make_unique<ox::PassThroughFS>(projectDir),
c.name);
if (kctx.error) {
return ox::Error{2, "failed to load project directory"};
}
Project project{*kctx.value, projectDir, projectDataDir};
return c.func(project, args + numCmdArgs);
}
}
return ox::Error{1, "command not found"};
}
}
return ox::Error{1, "module not found"};
} else {
// run app
auto const err = runApp(appName, projectDataDir, ox::UPtr<ox::FileSystem>{});
auto const err = runStudio(appName, projectDataDir, ox::UPtr<ox::FileSystem>{});
oxAssert(err, "Something went wrong...");
return err;
}
return {};
}
}

View File

@ -37,11 +37,15 @@ static bool shutdownHandler(turbine::Context &ctx) {
namespace ig {
extern bool s_mainWinHasFocus;
}
static ox::Vector<Module const*> modules;
static ox::Vector<Module const*> g_modules;
ox::Vector<Module const*> const &modules() noexcept {
return g_modules;
}
void registerModule(Module const*mod) noexcept {
if (mod) {
modules.emplace_back(mod);
g_modules.emplace_back(mod);
}
}
@ -443,7 +447,7 @@ void StudioUI::loadModule(Module const &mod) noexcept {
}
void StudioUI::loadModules() noexcept {
for (auto const mod : modules) {
for (auto const mod : g_modules) {
loadModule(*mod);
}
}
@ -673,14 +677,18 @@ ox::Error StudioUI::openProjectPath(ox::StringParam path) noexcept {
auto p = std::move(*pcIt);
std::ignore = config.projects.erase(pcIt);
auto &pc = *config.projects.emplace(0, std::move(p));
for (auto const &f: pc.openFiles) {
std::ignore = pc.openFiles.erase(
std::remove_if(
pc.openFiles.begin(), pc.openFiles.end(),
[&](ox::String const &f) {
auto const openFileErr = openFileActiveTab(f, pc.activeTabItemName == f);
if (openFileErr) {
oxErrorf("\nCould not open editor for file:\n\t{}\nReason:\n\t{}\n", f, toStr(openFileErr));
continue;
return true;
}
m_activeEditor = m_editors.back().value->get();
}
return false;
}));
} else {
config.projects.emplace(0, StudioConfig::ProjectConfig{
.projectPath = ox::String{m_project->projectPath()},

View File

@ -23,15 +23,34 @@ struct EditorMaker {
Func make;
};
struct Command {
ox::String name;
std::function<ox::Error(Project&, ox::SpanView<ox::CString>)> func;
Command(
ox::StringParam name,
std::function<ox::Error(Project&, ox::SpanView<ox::CString>)> func) noexcept:
name(std::move(name)),
func(std::move(func)) {}
};
class Module {
public:
virtual ~Module() noexcept = default;
virtual ox::Vector<EditorMaker> editors(studio::Context &ctx) const;
[[nodiscard]]
virtual ox::String id() const noexcept = 0;
virtual ox::Vector<ox::UPtr<ItemMaker>> itemMakers(studio::Context&) const;
[[nodiscard]]
virtual ox::Vector<Command> commands() const;
virtual ox::Vector<ox::UPtr<ItemTemplate>> itemTemplates(studio::Context&) const;
[[nodiscard]]
virtual ox::Vector<EditorMaker> editors(Context &ctx) const;
[[nodiscard]]
virtual ox::Vector<ox::UPtr<ItemMaker>> itemMakers(Context&) const;
[[nodiscard]]
virtual ox::Vector<ox::UPtr<ItemTemplate>> itemTemplates(Context&) const;
};

View File

@ -59,7 +59,7 @@ class Project: public ox::SignalHandler {
ox::HashMap<ox::String, ox::Vector<ox::String>> m_fileExtFileMap;
public:
explicit Project(keel::Context &ctx, ox::String path, ox::StringViewCR projectDataDir);
explicit Project(keel::Context &ctx, ox::StringParam path, ox::StringViewCR projectDataDir);
ox::Error create() noexcept;
@ -101,6 +101,16 @@ class Project: public ox::SignalHandler {
ox::Error deleteItem(ox::StringViewCR path) noexcept;
[[nodiscard]]
keel::Context &kctx() noexcept {
return m_kctx;
}
[[nodiscard]]
keel::Context const &kctx() const noexcept {
return m_kctx;
}
[[nodiscard]]
bool exists(ox::StringViewCR path) const noexcept;

View File

@ -6,6 +6,10 @@
namespace studio {
ox::Vector<Command> Module::commands() const {
return {};
}
ox::Vector<EditorMaker> Module::editors(Context&) const {
return {};
}

View File

@ -54,7 +54,7 @@ static constexpr bool isParentOf(ox::StringViewCR parent, ox::StringViewCR child
return beginsWith(sfmt("{}/", child), parent);
}
Project::Project(keel::Context &ctx, ox::String path, ox::StringViewCR projectDataDir):
Project::Project(keel::Context &ctx, ox::StringParam path, ox::StringViewCR projectDataDir):
m_kctx(ctx),
m_path(std::move(path)),
m_projectDataDir(projectDataDir),

View File

@ -50,7 +50,7 @@ OX_ALLOW_UNSAFE_BUFFERS_BEGIN
constexpr auto headerP1Len = ox::strlen(headerP2);
constexpr auto headerP2Len = ox::strlen(headerP1);
constexpr auto headerLen = headerP1Len + headerP2Len;
for (auto current = MEM_ROM; current < reinterpret_cast<char*>(0x0a000000); current += headerLen) {
for (auto current = MEM_ROM.data(); current < reinterpret_cast<char*>(0x0a000000); current += headerLen) {
if (memcmp(current, headerP1, headerP1Len) == 0 &&
memcmp(current + headerP1Len, headerP2, headerP2Len) == 0) {
return reinterpret_cast<std::size_t>(current + headerLen);