diff --git a/deps/gbabuildcore/base.cmake b/deps/gbabuildcore/base.cmake index 9b0b245..57678e2 100644 --- a/deps/gbabuildcore/base.cmake +++ b/deps/gbabuildcore/base.cmake @@ -1,8 +1,8 @@ list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake/modules) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -nostdlib") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -nostdinc++") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-unwind-tables") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions") +#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-unwind-tables") +#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-strict-aliasing") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mthumb-interwork") diff --git a/deps/ox/src/ox/model/typenamecatcher.hpp b/deps/ox/src/ox/model/typenamecatcher.hpp index ec65009..6d907c8 100644 --- a/deps/ox/src/ox/model/typenamecatcher.hpp +++ b/deps/ox/src/ox/model/typenamecatcher.hpp @@ -140,16 +140,16 @@ constexpr Str getModelTypeName() noexcept { return out; } -template +template [[nodiscard]] consteval auto requireModelTypeName() noexcept { - constexpr auto name = getModelTypeName(); + constexpr auto name = getModelTypeName(); static_assert(ox::StringView{name}.len(), "Type lacks required TypeName"); return name; } template -constexpr auto ModelTypeName_v = getModelTypeName(); +constexpr auto ModelTypeName_v = requireModelTypeName(); template constexpr auto ModelTypeVersion_v = requireModelTypeVersion(); diff --git a/deps/ox/src/ox/std/assert.cpp b/deps/ox/src/ox/std/assert.cpp index bda1262..aacd0f7 100644 --- a/deps/ox/src/ox/std/assert.cpp +++ b/deps/ox/src/ox/std/assert.cpp @@ -7,6 +7,7 @@ */ #include "fmt.hpp" +#include "realstd.hpp" #include "stacktrace.hpp" #include "trace.hpp" @@ -14,7 +15,7 @@ namespace ox { -void panic(StringViewCR file, int line, StringViewCR panicMsg, const Error &err) noexcept { +void panic(StringViewCR file, int const line, StringViewCR panicMsg, Error const&err) noexcept { oxErrf("\033[31;1;1mPANIC:\033[0m [{}:{}]: {}\n", file, line, panicMsg); if (err.msg) { oxErrf("\tError Message:\t{}\n", err.msg); @@ -32,16 +33,19 @@ void panic(StringViewCR file, int line, StringViewCR panicMsg, const Error &err) #endif } -void panic(const char *file, int line, const char *panicMsg, const Error &err) noexcept { +void panic(const char *file, int const line, char const*panicMsg, Error const&err) noexcept { panic(StringView{file}, line, StringView{panicMsg}, err); } -void assertFailFuncRuntime(StringViewCR file, int line, StringViewCR assertTxt, StringViewCR msg) noexcept { +void assertFailFuncRuntime( + StringViewCR file, + int const line, + StringViewCR assertTxt, + StringViewCR msg) noexcept { #ifdef OX_USE_STDLIB - auto output = sfmt("\n\033[31;1;1mASSERT FAILURE:\033[0m [{}:{}]: {}\n", file, line, msg); - output += genStackTrace(2); - oxTracef("assert", "Failed assert: {} ({}) [{}:{}]", msg, assertTxt, file, line); - std::abort(); + auto const st = genStackTrace(2); + oxTracef("assert", "Failed assert: {} ({}) [{}:{}]:\n{}", msg, assertTxt, file, line, st); + abort(); #else oxErrf("\n\033[31;1;1mASSERT FAILURE:\033[0m [{}:{}]: {}\n", file, line, msg); oxTracef("assert", "Failed assert: {} ({}) [{}:{}]", msg, assertTxt, file, line); @@ -49,7 +53,12 @@ void assertFailFuncRuntime(StringViewCR file, int line, StringViewCR assertTxt, #endif } -void assertFailFuncRuntime(StringViewCR file, int line, [[maybe_unused]] const Error &err, StringViewCR, StringViewCR assertMsg) noexcept { +void assertFailFuncRuntime( + StringViewCR file, + int const line, + [[maybe_unused]] Error const&err, + StringViewCR, + StringViewCR assertMsg) noexcept { #if defined(OX_USE_STDLIB) auto msg = sfmt("\n\033[31;1;1mASSERT FAILURE:\033[0m [{}:{}]: {}\n", file, line, assertMsg); if (err.msg) { @@ -62,7 +71,7 @@ void assertFailFuncRuntime(StringViewCR file, int line, [[maybe_unused]] const E msg += genStackTrace(2); oxErr(msg); oxTracef("assert", "Failed assert: {} [{}:{}]", assertMsg, file, line); - std::abort(); + abort(); #else constexprPanic(file, line, assertMsg); #endif diff --git a/deps/ox/src/ox/std/assert.hpp b/deps/ox/src/ox/std/assert.hpp index 7bce373..8573e6a 100644 --- a/deps/ox/src/ox/std/assert.hpp +++ b/deps/ox/src/ox/std/assert.hpp @@ -22,9 +22,15 @@ namespace ox { -void panic(StringViewCR file, int line, StringViewCR panicMsg, const Error &err = ox::Error(0)) noexcept; +[[noreturn]] +void panic(StringViewCR file, int line, StringViewCR panicMsg, Error const&err = {}) noexcept; -constexpr void constexprPanic(StringViewCR file, int line, StringViewCR panicMsg, const Error &err = ox::Error(0)) noexcept { +[[noreturn]] +constexpr void constexprPanic( + StringViewCR file, + int const line, + StringViewCR panicMsg, + Error const&err = {}) noexcept { if (!std::is_constant_evaluated()) { panic(file, line, panicMsg, err); } else { @@ -32,10 +38,24 @@ constexpr void constexprPanic(StringViewCR file, int line, StringViewCR panicMsg } } -void assertFailFuncRuntime(StringViewCR file, int line, StringViewCR assertTxt, StringViewCR msg) noexcept; -void assertFailFuncRuntime(StringViewCR file, int line, const Error &err, StringViewCR, StringViewCR assertMsg) noexcept; +void assertFailFuncRuntime( + StringViewCR file, + int line, + StringViewCR assertTxt, + StringViewCR msg) noexcept; +void assertFailFuncRuntime( + StringViewCR file, + int line, + Error const&err, + StringViewCR, + StringViewCR assertMsg) noexcept; -constexpr void assertFunc(StringViewCR file, int line, bool pass, [[maybe_unused]]StringViewCR assertTxt, [[maybe_unused]]StringViewCR msg) noexcept { +constexpr void assertFunc( + StringViewCR file, + int const line, + bool const pass, + [[maybe_unused]]StringViewCR assertTxt, + [[maybe_unused]]StringViewCR msg) noexcept { if (!pass) { if (!std::is_constant_evaluated()) { assertFailFuncRuntime(file, line, assertTxt, msg); @@ -45,7 +65,12 @@ constexpr void assertFunc(StringViewCR file, int line, bool pass, [[maybe_unused } } -constexpr void assertFunc(StringViewCR file, int line, const Error &err, StringViewCR, StringViewCR assertMsg) noexcept { +constexpr void assertFunc( + StringViewCR file, + int const line, + Error const&err, + StringViewCR, + StringViewCR assertMsg) noexcept { if (err) { if (!std::is_constant_evaluated()) { assertFailFuncRuntime(file, line, err, {}, assertMsg); @@ -55,7 +80,11 @@ constexpr void assertFunc(StringViewCR file, int line, const Error &err, StringV } } -constexpr void expect(StringViewCR file, int line, const auto &actual, const auto &expected) noexcept { +constexpr void expect( + StringViewCR file, + int const line, + auto const&actual, + auto const&expected) noexcept { if (actual != expected) { if (!std::is_constant_evaluated()) { #if defined(OX_USE_STDLIB) diff --git a/deps/ox/src/ox/std/error.hpp b/deps/ox/src/ox/std/error.hpp index 5e6885c..8161e10 100644 --- a/deps/ox/src/ox/std/error.hpp +++ b/deps/ox/src/ox/std/error.hpp @@ -80,13 +80,13 @@ struct Exception: public std::exception { ox::CString msg = nullptr; ErrorCode errCode = 0; - explicit inline Exception( + explicit Exception( ErrorCode const errCode, std::source_location const&src = std::source_location::current()) noexcept: src{src}, errCode{errCode} {} - explicit inline Exception( + explicit Exception( ErrorCode const errCode, ox::CString msg, std::source_location const&src = std::source_location::current()) noexcept: @@ -94,7 +94,7 @@ struct Exception: public std::exception { msg{msg}, errCode{errCode} {} - explicit inline Exception(Error const&err) noexcept: + explicit Exception(Error const&err) noexcept: src{err.src}, msg{err.msg ? err.msg : ""}, errCode{err.errCode} {} @@ -109,6 +109,7 @@ struct Exception: public std::exception { } }; +[[noreturn]] void panic(char const*file, int line, char const*panicMsg, Error const&err) noexcept; template diff --git a/deps/ox/src/ox/std/realstd.hpp b/deps/ox/src/ox/std/realstd.hpp index 8ce8691..ec2cff3 100644 --- a/deps/ox/src/ox/std/realstd.hpp +++ b/deps/ox/src/ox/std/realstd.hpp @@ -12,4 +12,13 @@ #include #else #define assert(e) while (!(e)); +#endif + +#if __has_include() +#include +#else +extern "C" { +[[noreturn]] +void abort(); +} #endif \ No newline at end of file diff --git a/deps/ox/src/ox/std/smallmap.hpp b/deps/ox/src/ox/std/smallmap.hpp index 881730f..869bfb1 100644 --- a/deps/ox/src/ox/std/smallmap.hpp +++ b/deps/ox/src/ox/std/smallmap.hpp @@ -85,6 +85,11 @@ class SmallMap { return m_pairs; } + [[nodiscard]] + constexpr ox::Span pairs() noexcept { + return m_pairs; + } + constexpr void clear(); private: diff --git a/deps/ox/src/ox/std/span.hpp b/deps/ox/src/ox/std/span.hpp index ddd978a..3114aa0 100644 --- a/deps/ox/src/ox/std/span.hpp +++ b/deps/ox/src/ox/std/span.hpp @@ -14,7 +14,7 @@ #include "iterator.hpp" #include "vector.hpp" -OX_CLANG_NOWARN_BEGIN(-Wunsafe-buffer-usage) +OX_ALLOW_UNSAFE_BUFFERS_BEGIN namespace ox { @@ -133,7 +133,7 @@ class Span { return m_items[i]; } - constexpr const T &operator[](std::size_t i) const noexcept { + constexpr T const&operator[](std::size_t i) const noexcept { ox::primitiveAssert(__FILE__, __LINE__, i < size(), "Span access overflow"); return m_items[i]; } @@ -168,8 +168,20 @@ class Span { }; template -using SpanView = Span; +using SpanView = Span; + +template +constexpr void spancpy(ox::Span const dst, ox::SpanView const src) noexcept { + auto const sz = ox::min(dst.size(), src.size()); + if (std::is_constant_evaluated() || std::is_trivially_copyable_v) { + for (size_t i{}; i < sz; ++i) { + dst.data()[i] = src.data()[i]; + } + } else { + memcpy(dst.data(), src.data(), sz * sizeof(T)); + } +} } -OX_CLANG_NOWARN_END +OX_ALLOW_UNSAFE_BUFFERS_END diff --git a/deps/ox/src/ox/std/typetraits.hpp b/deps/ox/src/ox/std/typetraits.hpp index 720ac05..f123ed0 100644 --- a/deps/ox/src/ox/std/typetraits.hpp +++ b/deps/ox/src/ox/std/typetraits.hpp @@ -19,12 +19,15 @@ namespace std { template -constexpr bool is_union_v = __is_union(T); +inline constexpr bool is_union_v = __is_union(T); -constexpr bool is_constant_evaluated() noexcept { +inline constexpr bool is_constant_evaluated() noexcept { return __builtin_is_constant_evaluated(); } +template +inline constexpr bool is_trivially_copyable_v = __is_trivially_copyable(T); + } #endif @@ -156,6 +159,9 @@ static_assert(is_class::value == false); template constexpr bool is_class_v = is_class(); +template +inline constexpr bool is_trivially_copyable_v = std::is_trivially_copyable_v; + template constexpr bool is_signed_v = integral_constant::value; diff --git a/deps/teagba/src/cstartup.cpp b/deps/teagba/src/cstartup.cpp index ea4a62c..e3845e0 100644 --- a/deps/teagba/src/cstartup.cpp +++ b/deps/teagba/src/cstartup.cpp @@ -4,17 +4,20 @@ #include +#include +#include + namespace mgba { void initConsole(); } -#define MEM_EWRAM_BEGIN reinterpret_cast(0x02000000) -#define MEM_EWRAM_END reinterpret_cast(0x0203FFFF) +#define MEM_HEAP_BEGIN reinterpret_cast(0x02000000) +#define MEM_HEAP_END reinterpret_cast(0x0203FFFF) -#define HEAP_BEGIN reinterpret_cast(MEM_EWRAM_BEGIN) +#define HEAP_BEGIN reinterpret_cast(MEM_HEAP_BEGIN) // set size to half of EWRAM -#define HEAP_SIZE ((MEM_EWRAM_END - MEM_EWRAM_BEGIN) / 2) -#define HEAP_END reinterpret_cast(MEM_EWRAM_BEGIN + HEAP_SIZE) +#define HEAP_SIZE ((MEM_HEAP_END - MEM_HEAP_BEGIN) / 2) +#define HEAP_END reinterpret_cast(MEM_HEAP_BEGIN + HEAP_SIZE) extern void (*__preinit_array_start[]) (void); extern void (*__preinit_array_end[]) (void); @@ -25,6 +28,14 @@ int main(int argc, const char **argv); extern "C" { +void abort() { + REG_IE = 0; + teagba::intrwait(0, 0); + while (true); +} + +void *__gxx_personality_v0{}; + void __libc_init_array() { auto preInits = __preinit_array_end - __preinit_array_start; for (decltype(preInits) i = 0; i < preInits; i++) { diff --git a/developer-handbook.md b/developer-handbook.md index 1ea62ca..b5168da 100644 --- a/developer-handbook.md +++ b/developer-handbook.md @@ -162,11 +162,9 @@ The Ox way of doing things is the Olympic way of doing things. ### Error Handling -The GBA build has exceptions disabled. -Instead of throwing exceptions, all engine code should return -[ox::Errors](deps/ox/ox-docs.md#error-handling) for error reporting. -For the sake of consistency, try to stick to ```ox::Error``` in non-engine code -as well, but non-engine code is free to use exceptions when they make sense. +Instead of throwing exceptions, generally try to use +[ox::Errors](deps/ox/ox-docs.md#error-handling) for error reporting, +but exceptions may be used where they make sense. Exceptions should generally just use ```OxException```, which is bascially an exception form of ```ox::Error```. diff --git a/src/nostalgia/modules/core/include/nostalgia/core/gfx.hpp b/src/nostalgia/modules/core/include/nostalgia/core/gfx.hpp index c8c8ed7..e5f32be 100644 --- a/src/nostalgia/modules/core/include/nostalgia/core/gfx.hpp +++ b/src/nostalgia/modules/core/include/nostalgia/core/gfx.hpp @@ -142,6 +142,10 @@ ox::Error loadBgTileSheet( unsigned cbb, TileSheetSet const&set) noexcept; +void clearCbb(Context &ctx, unsigned cbb) noexcept; + +void clearCbbs(Context &ctx) noexcept; + ox::Error loadBgTileSheet( Context &ctx, unsigned cbb, diff --git a/src/nostalgia/modules/core/include/nostalgia/core/studio.hpp b/src/nostalgia/modules/core/include/nostalgia/core/studio.hpp new file mode 100644 index 0000000..0e6ad2c --- /dev/null +++ b/src/nostalgia/modules/core/include/nostalgia/core/studio.hpp @@ -0,0 +1,11 @@ +/* + * Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#pragma once + +#include + +namespace nostalgia::core { + +} diff --git a/src/nostalgia/modules/core/include/nostalgia/core/tilesheet.hpp b/src/nostalgia/modules/core/include/nostalgia/core/tilesheet.hpp index 532de0d..6348903 100644 --- a/src/nostalgia/modules/core/include/nostalgia/core/tilesheet.hpp +++ b/src/nostalgia/modules/core/include/nostalgia/core/tilesheet.hpp @@ -18,6 +18,23 @@ namespace nostalgia::core { +struct SubSheetTemplate { + static constexpr auto TypeName = "net.drinkingtea.nostalgia.gfx.SubSheetTemplate"; + static constexpr auto TypeVersion = 1; + ox::String name; + int32_t width{}; + int32_t height{}; + ox::Vector subsheets; +}; + +OX_MODEL_BEGIN(SubSheetTemplate) + OX_MODEL_FIELD(name) + OX_MODEL_FIELD(width) + OX_MODEL_FIELD(height) + OX_MODEL_FIELD(subsheets) +OX_MODEL_END() + + // Predecessor to TileSheet, kept for backward compatibility struct TileSheetV1 { static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.NostalgiaGraphic"; @@ -237,10 +254,12 @@ struct TileSheetV4 { [[nodiscard]] constexpr bool valid(TileSheetV4::SubSheet const&ss, int bpp) noexcept { auto const bytes = static_cast(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); - }); + return + (ss.pixels.empty() || ss.subsheets.empty()) && + ox::all_of(ss.subsheets.begin(), ss.subsheets.end(), + [bpp, bytes](TileSheetV4::SubSheet const&s) { + return bytes == s.pixels.size() && valid(s, bpp); + }); } [[nodiscard]] @@ -249,8 +268,14 @@ constexpr bool valid(TileSheetV4 const&ts) noexcept { } constexpr void repair(TileSheetV4::SubSheet &ss, int bpp) noexcept { - auto const bytes = static_cast(ss.columns * ss.rows * PixelsPerTile) / (bpp == 4 ? 2 : 1); - ss.pixels.resize(bytes); + if (ss.subsheets.empty()) { + auto const bytes = static_cast(ss.columns * ss.rows * PixelsPerTile) / (bpp == 4 ? 2 : 1); + ss.pixels.resize(bytes); + } else { + ss.pixels.clear(); + ss.columns = -1; + ss.rows = -1; + } for (auto &s : ss.subsheets) { repair(s, bpp); } diff --git a/src/nostalgia/modules/core/src/gba/gfx.cpp b/src/nostalgia/modules/core/src/gba/gfx.cpp index 59e2528..eac447b 100644 --- a/src/nostalgia/modules/core/src/gba/gfx.cpp +++ b/src/nostalgia/modules/core/src/gba/gfx.cpp @@ -63,6 +63,19 @@ ox::Error loadSpritePalette( return {}; } +void clearCbb(Context&, unsigned const cbb) noexcept { + for (auto &v : MEM_BG_TILES[cbb]) { + v = 0; + } +} + +void clearCbbs(Context &ctx) noexcept { + clearCbb(ctx, 0); + clearCbb(ctx, 1); + clearCbb(ctx, 2); + clearCbb(ctx, 3); +} + static ox::Error loadTileSheetSet( Context &ctx, ox::Span tileMapTargetMem, @@ -99,10 +112,10 @@ ox::Error loadBgTileSheet( 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 cnt = (tileCnt * bytesPerTile) / 2; auto const srcPxIdx = srcTileIdx * bytesPerTile; auto const dstPxIdx = (dstTileIdx * bytesPerTile) / 2; - for (size_t i = 0; i < pixCnt; ++i) { + for (size_t i = 0; i < cnt; ++i) { auto const srcIdx = srcPxIdx + i * 2; auto const p1 = static_cast(ts.pixels[srcIdx]); auto const p2 = static_cast(ts.pixels[srcIdx + 1]); diff --git a/src/nostalgia/modules/core/src/gba/panic.cpp b/src/nostalgia/modules/core/src/gba/panic.cpp index a009318..1014a4a 100644 --- a/src/nostalgia/modules/core/src/gba/panic.cpp +++ b/src/nostalgia/modules/core/src/gba/panic.cpp @@ -4,6 +4,7 @@ #include +#include #include #include @@ -51,9 +52,7 @@ OX_ALLOW_UNSAFE_BUFFERS_END if (err.src.file_name() != nullptr) { oxErrf("\tError Location:\t{}:{}\n", err.src.file_name(), err.src.line()); } - // disable all interrupt handling and IntrWait on no interrupts - REG_IE = 0; - teagba::intrwait(0, 0); + abort(); } } diff --git a/src/nostalgia/modules/core/src/keel/keelmodule.cpp b/src/nostalgia/modules/core/src/keel/keelmodule.cpp index 8f788d6..e56d90d 100644 --- a/src/nostalgia/modules/core/src/keel/keelmodule.cpp +++ b/src/nostalgia/modules/core/src/keel/keelmodule.cpp @@ -67,7 +67,7 @@ static class: public keel::Module { ox::Vector packTransforms() const noexcept final { return { // convert tilesheets to CompactTileSheets - [](keel::Context &ctx, ox::Buffer &buff, ox::StringView typeId) -> ox::Result { + [](keel::Context &ctx, ox::Buffer &buff, ox::StringViewCR typeId) -> ox::Result { if (typeId == ox::ModelTypeId_v || typeId == ox::ModelTypeId_v || typeId == ox::ModelTypeId_v || @@ -78,7 +78,7 @@ static class: public keel::Module { } return false; }, - [](keel::Context &ctx, ox::Buffer &buff, ox::StringView typeId) -> ox::Result { + [](keel::Context &ctx, ox::Buffer &buff, ox::StringViewCR typeId) -> ox::Result { if (typeId == ox::ModelTypeId_v || typeId == ox::ModelTypeId_v || typeId == ox::ModelTypeId_v || diff --git a/src/nostalgia/modules/core/src/opengl/gfx.cpp b/src/nostalgia/modules/core/src/opengl/gfx.cpp index 41a1413..0357237 100644 --- a/src/nostalgia/modules/core/src/opengl/gfx.cpp +++ b/src/nostalgia/modules/core/src/opengl/gfx.cpp @@ -111,15 +111,15 @@ static constexpr auto bgVertexRow(uint_t x, uint_t y) noexcept { } static void setSpriteBufferObject( - uint_t vi, - float enabled, + uint_t const vi, + float const enabled, float x, float y, - uint_t textureRow, - uint_t flipX, - uint_t priority, - float *vbo, - GLuint *ebo) noexcept { + uint_t const textureRow, + uint_t const flipX, + uint_t const priority, + ox::Span const vbo, + ox::Span const ebo) noexcept { // don't worry, this memcpy gets optimized to something much more ideal constexpr float xmod = 0.1f; constexpr float ymod = 0.1f; @@ -138,25 +138,25 @@ static void setSpriteBufferObject( enabled, x + xmod, y + ymod, prif, R, textureRowf + 0, // top right enabled, x, y + ymod, prif, L, textureRowf + 0, // top left }; - memcpy(vbo, vertices.data(), sizeof(vertices)); + ox::spancpy(vbo, vertices); ox::Array const elms { - vi + 0, vi + 1, vi + 2, - vi + 2, vi + 3, vi + 0, + vi + 0, vi + 1, vi + 2, + vi + 2, vi + 3, vi + 0, }; - memcpy(ebo, elms.data(), sizeof(elms)); + ox::spancpy(ebo, elms); } static void setTileBufferObject( - uint_t vi, + uint_t const vi, float x, float y, - float textureTileIdx, - float priority, - float palOffset, - bool flipX, - bool flipY, - float *vbo, - GLuint *ebo) noexcept { + float const textureTileIdx, + float const priority, + float const palOffset, + bool const flipX, + bool const flipY, + ox::Span const vbo, + ox::Span const ebo) noexcept { // don't worry, this memcpy gets optimized to something much more ideal constexpr float ymod = 0.1f; constexpr float xmod = 0.1f; @@ -170,24 +170,35 @@ static void setTileBufferObject( float const T = flipY ? 1 : 0; float const B = flipY ? 0 : 1; ox::Array const vertices { - x, y, prif, L, B, textureTileIdx, palOffset, // bottom left - x + xmod, y, prif, R, B, textureTileIdx, palOffset, // bottom right - x + xmod, y + ymod, prif, R, T, textureTileIdx, palOffset, // top right - x, y + ymod, prif, L, T, textureTileIdx, palOffset, // top left + x, y, prif, L, B, textureTileIdx, palOffset, // bottom left + x + xmod, y, prif, R, B, textureTileIdx, palOffset, // bottom right + x + xmod, y + ymod, prif, R, T, textureTileIdx, palOffset, // top right + x, y + ymod, prif, L, T, textureTileIdx, palOffset, // top left }; - memcpy(vbo, vertices.data(), sizeof(vertices)); + ox::spancpy(vbo, vertices); ox::Array const elms { - vi + 0, vi + 1, vi + 2, - vi + 2, vi + 3, vi + 0, + vi + 0, vi + 1, vi + 2, + vi + 2, vi + 3, vi + 0, }; - memcpy(ebo, elms.data(), sizeof(elms)); + ox::spancpy(ebo, elms); } static void initSpriteBufferObjects(Context &ctx, glutils::BufferSet &bs) noexcept { for (auto i = 0u; i < ctx.spriteCount; ++i) { - auto vbo = &bs.vertices[i * static_cast(SpriteVertexVboLength)]; - auto ebo = &bs.elements[i * static_cast(SpriteVertexEboLength)]; - setSpriteBufferObject(i * static_cast(SpriteVertexVboRows) * ctx.blocksPerSprite, 0, 0, 0, 0, false, 0, vbo, ebo); + auto const vbo = ox::Span{bs.vertices} + + i * static_cast(SpriteVertexVboLength); + auto const ebo = ox::Span{bs.elements} + + i * static_cast(SpriteVertexEboLength); + setSpriteBufferObject( + i * static_cast(SpriteVertexVboRows) * ctx.blocksPerSprite, + 0, + 0, + 0, + 0, + false, + 0, + vbo, + ebo); } } @@ -195,8 +206,10 @@ static void initBackgroundBufferObjects(glutils::BufferSet &bs) noexcept { for (auto x = 0u; x < TileColumns; ++x) { for (auto y = 0u; y < TileRows; ++y) { const auto i = bgVertexRow(x, y); - auto vbo = &bs.vertices[i * static_cast(BgVertexVboLength)]; - auto ebo = &bs.elements[i * static_cast(BgVertexEboLength)]; + auto const vbo = ox::Span{bs.vertices} + + i * static_cast(BgVertexVboLength); + auto const ebo = ox::Span{bs.elements} + + i * static_cast(BgVertexEboLength); setTileBufferObject( static_cast(i * BgVertexVboRows), static_cast(x), @@ -421,8 +434,8 @@ static void setSprite( auto const eboIdx = eboBase + renderer::SpriteVertexEboLength * i; oxAssert(vboIdx < ctx.spriteBlocks.vertices.size(), "vbo overflow"); oxAssert(eboIdx < ctx.spriteBlocks.elements.size(), "ebo overflow"); - auto const vbo = &ctx.spriteBlocks.vertices[vboIdx]; - auto const ebo = &ctx.spriteBlocks.elements[eboIdx]; + auto const vbo = ox::Span{ctx.spriteBlocks.vertices} + vboIdx; + auto const ebo = ox::Span{ctx.spriteBlocks.elements} + eboIdx; renderer::setSpriteBufferObject( static_cast(vboIdx), enabled, @@ -556,7 +569,7 @@ static void copyPixels( CompactTileSheet const&ts, ox::Span dst, size_t const srcPxIdx, - size_t pxlCnt) noexcept { + size_t const pxlCnt) noexcept { size_t idx{}; if (ts.bpp == 4) { for (size_t i = 0; i < pxlCnt; i += 2) { @@ -573,6 +586,18 @@ static void copyPixels( } } +void clearCbb(Context &ctx, unsigned const cbb) noexcept { + for (auto &v : ctx.cbbs[cbb].pixels) { + v = 0; + } +} + +void clearCbbs(Context &ctx) noexcept { + for (unsigned i = 0 ; i < ctx.cbbs.size(); ++i) { + clearCbb(ctx, i); + } +} + ox::Error loadBgTileSheet( Context &ctx, unsigned const cbb, @@ -656,8 +681,8 @@ void setBgTile( const auto x = static_cast(column); const auto i = renderer::bgVertexRow(x, y); auto &cbb = ctx.cbbs[z]; - const auto vbo = &cbb.vertices[i * renderer::BgVertexVboLength]; - const auto ebo = &cbb.elements[i * renderer::BgVertexEboLength]; + const auto vbo = ox::Span{cbb.vertices} + i * renderer::BgVertexVboLength; + const auto ebo = ox::Span{cbb.elements} + i * renderer::BgVertexEboLength; auto &bg = ctx.backgrounds[bgIdx]; renderer::setTileBufferObject( static_cast(i * renderer::BgVertexVboRows), diff --git a/src/nostalgia/modules/core/src/studio/CMakeLists.txt b/src/nostalgia/modules/core/src/studio/CMakeLists.txt index df7bfad..8c5e958 100644 --- a/src/nostalgia/modules/core/src/studio/CMakeLists.txt +++ b/src/nostalgia/modules/core/src/studio/CMakeLists.txt @@ -3,7 +3,6 @@ add_library(NostalgiaCore-Studio) add_library( NostalgiaCore-Studio-ImGui studiomodule.cpp - tilesheeteditor/tilesheeteditor-imgui.cpp ) target_link_libraries( @@ -15,7 +14,6 @@ target_link_libraries( target_link_libraries( NostalgiaCore-Studio-ImGui PUBLIC NostalgiaCore-Studio - Studio ) install( diff --git a/src/nostalgia/modules/core/src/studio/paletteeditor/CMakeLists.txt b/src/nostalgia/modules/core/src/studio/paletteeditor/CMakeLists.txt index 862d43f..245c5e9 100644 --- a/src/nostalgia/modules/core/src/studio/paletteeditor/CMakeLists.txt +++ b/src/nostalgia/modules/core/src/studio/paletteeditor/CMakeLists.txt @@ -9,5 +9,9 @@ target_sources( commands/renamepagecommand.cpp commands/updatecolorcommand.cpp commands/updatecolorinfocommand.cpp +) + +target_sources( + NostalgiaCore-Studio-ImGui PRIVATE paletteeditor-imgui.cpp ) diff --git a/src/nostalgia/modules/core/src/studio/tilesheeteditor/CMakeLists.txt b/src/nostalgia/modules/core/src/studio/tilesheeteditor/CMakeLists.txt index bc92afd..01d3e67 100644 --- a/src/nostalgia/modules/core/src/studio/tilesheeteditor/CMakeLists.txt +++ b/src/nostalgia/modules/core/src/studio/tilesheeteditor/CMakeLists.txt @@ -6,6 +6,11 @@ target_sources( tilesheetpixels.cpp ) +target_sources( + NostalgiaCore-Studio-ImGui PRIVATE + tilesheeteditor-imgui.cpp +) + target_link_libraries( NostalgiaCore-Studio-ImGui PUBLIC lodepng diff --git a/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/inserttilescommand.cpp b/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/inserttilescommand.cpp index 3a98643..9651fee 100644 --- a/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/inserttilescommand.cpp +++ b/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/inserttilescommand.cpp @@ -6,7 +6,7 @@ namespace nostalgia::core { -core::InsertTilesCommand::InsertTilesCommand( +InsertTilesCommand::InsertTilesCommand( TileSheet &img, TileSheet::SubSheetIdx idx, std::size_t tileIdx, @@ -31,9 +31,11 @@ ox::Error InsertTilesCommand::redo() noexcept { auto &s = getSubSheet(m_img, m_idx); auto &p = s.pixels; auto dstPos = m_insertPos + m_insertCnt; - auto const dst = &p[dstPos]; auto const src = &p[m_insertPos]; - ox::memmove(dst, src, p.size() - dstPos); + if (dstPos < p.size()) { + auto const dst = &p[dstPos]; + ox::memmove(dst, src, p.size() - dstPos); + } ox::memset(src, 0, m_insertCnt * sizeof(decltype(p[0]))); return {}; } @@ -41,12 +43,14 @@ ox::Error InsertTilesCommand::redo() noexcept { ox::Error InsertTilesCommand::undo() noexcept { auto &s = getSubSheet(m_img, m_idx); auto &p = s.pixels; - 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); + auto const srcIdx = m_insertPos + m_insertCnt; + if (srcIdx < p.size()) { + auto const sz = p.size() - srcIdx; + auto const src = &p[srcIdx]; + ox::memmove(dst1, src, sz); + } ox::memcpy(dst2, m_deletedPixels.data(), m_deletedPixels.size()); return {}; } diff --git a/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/updatesubsheetcommand.cpp b/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/updatesubsheetcommand.cpp index f5e3806..f15a360 100644 --- a/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/updatesubsheetcommand.cpp +++ b/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/updatesubsheetcommand.cpp @@ -9,27 +9,24 @@ namespace nostalgia::core { core::UpdateSubSheetCommand::UpdateSubSheetCommand( TileSheet &img, TileSheet::SubSheetIdx idx, - ox::String name, - int cols, - int rows) noexcept: - m_img(img), - m_idx(std::move(idx)), - m_sheet(getSubSheet(m_img, m_idx)), - m_newName(std::move(name)), - m_newCols(cols), - m_newRows(rows) { + ox::StringParam name, + int const cols, + int const rows): + m_img{img}, + m_idx{std::move(idx)}, + m_sheet{getSubSheet(m_img, m_idx)} { + m_sheet = getSubSheet(m_img, m_idx); + m_sheet.name = std::move(name); + OX_THROW_ERROR(resizeSubsheet(m_sheet, m_img.bpp, {cols, rows})); } ox::Error UpdateSubSheetCommand::redo() noexcept { - auto &sheet = getSubSheet(m_img, m_idx); - sheet.name = m_newName; - oxLogError(resizeSubsheet(sheet, m_img.bpp, {m_newCols, m_newRows})); + std::swap(m_sheet, getSubSheet(m_img, m_idx)); return {}; } ox::Error UpdateSubSheetCommand::undo() noexcept { - auto &sheet = getSubSheet(m_img, m_idx); - sheet = m_sheet; + std::swap(m_sheet, getSubSheet(m_img, m_idx)); return {}; } diff --git a/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/updatesubsheetcommand.hpp b/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/updatesubsheetcommand.hpp index 7bdb617..9a90fee 100644 --- a/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/updatesubsheetcommand.hpp +++ b/src/nostalgia/modules/core/src/studio/tilesheeteditor/commands/updatesubsheetcommand.hpp @@ -13,17 +13,14 @@ class UpdateSubSheetCommand: public TileSheetCommand { TileSheet &m_img; TileSheet::SubSheetIdx m_idx; TileSheet::SubSheet m_sheet; - ox::String m_newName; - int m_newCols = 0; - int m_newRows = 0; public: UpdateSubSheetCommand( TileSheet &img, TileSheet::SubSheetIdx idx, - ox::String name, + ox::StringParam name, int cols, - int rows) noexcept; + int rows); ox::Error redo() noexcept final; diff --git a/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp b/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp index ad047ab..ccce34e 100644 --- a/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp +++ b/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp @@ -188,7 +188,7 @@ void TileSheetEditorImGui::draw(studio::StudioContext&) noexcept { 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}}); + m_model.setSelection({{}, {img.columns * TileWidth - 1, img.rows * TileHeight - 1}}); } else if (ImGui::IsKeyPressed(ImGuiKey_G)) { m_model.clearSelection(); } @@ -544,7 +544,7 @@ void TileSheetEditorImGui::SubSheetEditor::draw(turbine::Context &tctx) noexcept auto const popupHeight = modSize ? 130.f : 85.f; auto const popupSz = ImVec2{popupWidth, popupHeight}; if (ig::BeginPopup(tctx, popupName, m_show, popupSz)) { - ImGui::InputText("Name", m_name.data(), m_name.cap()); + ig::InputText("Name", m_name); if (modSize) { ImGui::InputInt("Columns", &m_cols); ImGui::InputInt("Rows", &m_rows); diff --git a/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditormodel.cpp b/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditormodel.cpp index b3acb13..c31620f 100644 --- a/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditormodel.cpp +++ b/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditormodel.cpp @@ -26,11 +26,6 @@ namespace nostalgia::core { -Palette const TileSheetEditorModel::s_defaultPalette = { - .colorNames = {ox::Vector{{}}}, - .pages = {{"Page 1", ox::Vector(128)}}, -}; - // delete pixels of all non-leaf nodes static void normalizeSubsheets(TileSheet::SubSheet &ss) noexcept { if (ss.subsheets.empty()) { @@ -42,7 +37,14 @@ static void normalizeSubsheets(TileSheet::SubSheet &ss) noexcept { } } -TileSheetEditorModel::TileSheetEditorModel(studio::StudioContext &sctx, ox::StringView path, studio::UndoStack &undoStack): + +Palette const TileSheetEditorModel::s_defaultPalette = { + .colorNames = {ox::Vector{{}}}, + .pages = {{"Page 1", ox::Vector(128)}}, +}; + +TileSheetEditorModel::TileSheetEditorModel( + studio::StudioContext &sctx, ox::StringViewCR path, studio::UndoStack &undoStack): m_sctx(sctx), m_tctx(m_sctx.tctx), m_path(path), @@ -62,7 +64,7 @@ void TileSheetEditorModel::cut() { TileSheetClipboard blankCb; auto cb = ox::make_unique(); auto const&s = activeSubSheet(); - iterateSelectionRows(*m_selection, [&](int x, int y) { + iterateSelectionRows(*m_selection, [&](int const x, int const y) { auto pt = ox::Point{x, y}; auto const idx = core::idx(s, pt); auto const c = getPixel(s, m_img.bpp, idx); @@ -73,7 +75,8 @@ void TileSheetEditorModel::cut() { 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(CommandId::Cut, m_img, m_activeSubsSheetIdx, pt1, pt2, blankCb)); + pushCommand(ox::make( + CommandId::Cut, m_img, m_activeSubsSheetIdx, pt1, pt2, blankCb)); } void TileSheetEditorModel::copy() { @@ -81,7 +84,7 @@ void TileSheetEditorModel::copy() { return; } auto cb = ox::make_unique(); - iterateSelectionRows(*m_selection, [&](int x, int y) { + iterateSelectionRows(*m_selection, [&](int const x, int const y) { auto pt = ox::Point{x, y}; const auto&s = activeSubSheet(); const auto idx = core::idx(s, pt); @@ -105,7 +108,8 @@ void TileSheetEditorModel::paste() { auto const&s = activeSubSheet(); auto const pt1 = m_selection->a; auto const pt2 = ox::Point{s.columns * TileWidth, s.rows * TileHeight}; - pushCommand(ox::make(CommandId::Paste, m_img, m_activeSubsSheetIdx, pt1, pt2, *cb)); + pushCommand(ox::make( + CommandId::Paste, m_img, m_activeSubsSheetIdx, pt1, pt2, *cb)); } bool TileSheetEditorModel::acceptsClipboardPayload() const noexcept { @@ -120,8 +124,8 @@ ox::StringView TileSheetEditorModel::palPath() const noexcept { } constexpr ox::StringView uuidPrefix = "uuid://"; if (ox::beginsWith(path, uuidPrefix)) { - auto uuid = ox::StringView(&path[uuidPrefix.bytes()], path.bytes() - uuidPrefix.bytes()); - auto out = keelCtx(m_tctx).uuidToPath.at(uuid); + auto const uuid = ox::StringView(&path[uuidPrefix.bytes()], path.bytes() - uuidPrefix.bytes()); + auto const out = keelCtx(m_tctx).uuidToPath.at(uuid); if (out.error) { return {}; } @@ -131,13 +135,14 @@ ox::StringView TileSheetEditorModel::palPath() const noexcept { } } -ox::Error TileSheetEditorModel::setPalette(ox::StringView path) noexcept { +ox::Error TileSheetEditorModel::setPalette(ox::StringViewCR path) noexcept { OX_REQUIRE(uuid, keelCtx(m_tctx).pathToUuid.at(path)); - pushCommand(ox::make(activeSubSheetIdx(), m_img, uuid->toString())); + pushCommand(ox::make( + activeSubSheetIdx(), m_img, uuid->toString())); return {}; } -void TileSheetEditorModel::setPalettePage(size_t pg) noexcept { +void TileSheetEditorModel::setPalettePage(size_t const pg) noexcept { m_palettePage = ox::clamp(pg, 0, m_pal->pages.size() - 1); m_updated = true; } @@ -146,7 +151,7 @@ size_t TileSheetEditorModel::palettePage() const noexcept { return m_palettePage; } -void TileSheetEditorModel::drawCommand(ox::Point const&pt, std::size_t palIdx) noexcept { +void TileSheetEditorModel::drawCommand(ox::Point const&pt, std::size_t const palIdx) noexcept { const auto &activeSubSheet = getSubSheet(m_img, m_activeSubsSheetIdx); if (pt.x >= activeSubSheet.columns * TileWidth || pt.y >= activeSubSheet.rows * TileHeight) { return; @@ -155,7 +160,8 @@ void TileSheetEditorModel::drawCommand(ox::Point const&pt, std::size_t palIdx) n if (m_ongoingDrawCommand) { m_updated = m_updated || m_ongoingDrawCommand->append(idx); } else if (getPixel(activeSubSheet, m_img.bpp, idx) != palIdx) { - pushCommand(ox::make(m_img, m_activeSubsSheetIdx, idx, static_cast(palIdx))); + pushCommand(ox::make( + m_img, m_activeSubsSheetIdx, idx, static_cast(palIdx))); } } @@ -171,16 +177,20 @@ void TileSheetEditorModel::rmSubsheet(TileSheet::SubSheetIdx const&idx) noexcept pushCommand(ox::make(m_img, idx)); } -void TileSheetEditorModel::insertTiles(TileSheet::SubSheetIdx const&idx, std::size_t tileIdx, std::size_t tileCnt) noexcept { +void TileSheetEditorModel::insertTiles( + TileSheet::SubSheetIdx const&idx, std::size_t const tileIdx, std::size_t const tileCnt) noexcept { pushCommand(ox::make(m_img, idx, tileIdx, tileCnt)); } -void TileSheetEditorModel::deleteTiles(TileSheet::SubSheetIdx const&idx, std::size_t tileIdx, std::size_t tileCnt) noexcept { +void TileSheetEditorModel::deleteTiles( + TileSheet::SubSheetIdx const&idx, std::size_t const tileIdx, std::size_t const tileCnt) noexcept { pushCommand(ox::make(m_img, idx, tileIdx, tileCnt)); } -ox::Error TileSheetEditorModel::updateSubsheet(TileSheet::SubSheetIdx const&idx, ox::StringView const&name, int cols, int rows) noexcept { - pushCommand(ox::make(m_img, idx, ox::String(name), cols, rows)); +ox::Error TileSheetEditorModel::updateSubsheet( + TileSheet::SubSheetIdx const&idx, ox::StringViewCR name, int const cols, int const rows) noexcept { + OX_REQUIRE(cmd, ox::makeCatch(m_img, idx, name, cols, rows)); + pushCommand(cmd); return {}; } @@ -189,7 +199,7 @@ void TileSheetEditorModel::setActiveSubsheet(TileSheet::SubSheetIdx const&idx) n this->activeSubsheetChanged.emit(m_activeSubsSheetIdx); } -void TileSheetEditorModel::fill(ox::Point const&pt, int palIdx) noexcept { +void TileSheetEditorModel::fill(ox::Point const&pt, int const palIdx) noexcept { auto const&activeSubSheet = getSubSheet(m_img, m_activeSubsSheetIdx); // build idx list if (pt.x >= activeSubSheet.columns * TileWidth || pt.y >= activeSubSheet.rows * TileHeight) { @@ -197,10 +207,10 @@ void TileSheetEditorModel::fill(ox::Point const&pt, int palIdx) noexcept { } ox::Array updateMap = {}; auto const oldColor = getPixel(activeSubSheet, m_img.bpp, pt); - getFillPixels(updateMap, pt, oldColor); + getFillPixels(activeSubSheet, updateMap, pt, oldColor); ox::Vector idxList; auto i = core::idx(activeSubSheet, pt) / PixelsPerTile * PixelsPerTile; - for (auto u : updateMap) { + for (auto const u : updateMap) { if (u) { idxList.emplace_back(i); } @@ -230,7 +240,7 @@ void TileSheetEditorModel::completeSelection() noexcept { m_selTracker.finishSelection(); m_selection.emplace(m_selTracker.selection()); auto&pt = m_selection->b; - auto&s = activeSubSheet(); + auto const&s = activeSubSheet(); pt.x = ox::min(s.columns * TileWidth - 1, pt.x); pt.y = ox::min(s.rows * TileHeight - 1, pt.y); } @@ -275,47 +285,44 @@ ox::Error TileSheetEditorModel::saveFile() noexcept { return m_sctx.project->writeObj(m_path, m_img, ox::ClawFormat::Metal); } -bool TileSheetEditorModel::pixelSelected(std::size_t idx) const noexcept { +bool TileSheetEditorModel::pixelSelected(std::size_t const idx) const noexcept { auto const&s = activeSubSheet(); auto const pt = idxToPt(static_cast(idx), s.columns); return m_selection && m_selection->contains(pt); } -void TileSheetEditorModel::getFillPixels(ox::Span 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; - }; - // get points - const auto leftPt = pt + ox::Point(-1, 0); - const auto rightPt = pt + ox::Point(1, 0); - const auto topPt = pt + ox::Point(0, -1); - const auto bottomPt = pt + ox::Point(0, 1); - // calculate indices - const auto idx = ptToIdx(pt, activeSubSheet.columns); - const auto leftIdx = ptToIdx(leftPt, activeSubSheet.columns); - const auto rightIdx = ptToIdx(rightPt, activeSubSheet.columns); - const auto topIdx = ptToIdx(topPt, activeSubSheet.columns); - const auto bottomIdx = ptToIdx(bottomPt, activeSubSheet.columns); - const auto tile = tileIdx(pt); +void TileSheetEditorModel::getFillPixels( + TileSheet::SubSheet const&activeSubSheet, + ox::Span pixels, + ox::Point const&pt, + int const oldColor) const noexcept { + auto const idx = ptToIdx(pt, activeSubSheet.columns); + auto const relIdx = idx % PixelsPerTile; + if (pixels[relIdx] || getPixel(activeSubSheet, m_img.bpp, idx) != oldColor) { + return; + } // mark pixels to update - pixels[idx % PixelsPerTile] = true; - if (!pixels[leftIdx % PixelsPerTile] && tile == tileIdx(leftPt) && getPixel(activeSubSheet, m_img.bpp, leftIdx) == oldColor) { - getFillPixels(pixels, leftPt, oldColor); + pixels[relIdx] = true; + if (pt.x % TileWidth != 0) { + auto const leftPt = pt + ox::Point{-1, 0}; + getFillPixels(activeSubSheet, pixels, leftPt, oldColor); } - if (!pixels[rightIdx % PixelsPerTile] && tile == tileIdx(rightPt) && getPixel(activeSubSheet, m_img.bpp, rightIdx) == oldColor) { - getFillPixels(pixels, rightPt, oldColor); + if (pt.x % TileWidth != TileWidth - 1) { + auto const rightPt = pt + ox::Point{1, 0}; + getFillPixels(activeSubSheet, pixels, rightPt, oldColor); } - if (!pixels[topIdx % PixelsPerTile] && tile == tileIdx(topPt) && getPixel(activeSubSheet, m_img.bpp, topIdx) == oldColor) { - getFillPixels(pixels, topPt, oldColor); + if (pt.y % TileHeight != 0) { + auto const topPt = pt + ox::Point{0, -1}; + getFillPixels(activeSubSheet, pixels, topPt, oldColor); } - if (!pixels[bottomIdx % PixelsPerTile] && tile == tileIdx(bottomPt) && getPixel(activeSubSheet, m_img.bpp, bottomIdx) == oldColor) { - getFillPixels(pixels, bottomPt, oldColor); + if (pt.y % TileHeight != TileHeight - 1) { + auto const bottomPt = pt + ox::Point{0, 1}; + getFillPixels(activeSubSheet, pixels, bottomPt, oldColor); } } void TileSheetEditorModel::pushCommand(studio::UndoCommand *cmd) noexcept { - std::ignore = m_undoStack.push(ox::UPtr(cmd)); + std::ignore = m_undoStack.push(ox::UPtr{cmd}); m_ongoingDrawCommand = dynamic_cast(cmd); m_updated = true; } diff --git a/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditormodel.hpp b/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditormodel.hpp index 6dd03b6..6371a4e 100644 --- a/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditormodel.hpp +++ b/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditormodel.hpp @@ -4,9 +4,7 @@ #pragma once -#include #include -#include #include #include @@ -38,7 +36,7 @@ class TileSheetEditorModel: public ox::SignalHandler { bool m_updated = false; public: - TileSheetEditorModel(studio::StudioContext &sctx, ox::StringView path, studio::UndoStack &undoStack); + TileSheetEditorModel(studio::StudioContext &sctx, ox::StringViewCR path, studio::UndoStack &undoStack); ~TileSheetEditorModel() override = default; @@ -63,7 +61,7 @@ class TileSheetEditorModel: public ox::SignalHandler { [[nodiscard]] ox::StringView palPath() const noexcept; - ox::Error setPalette(ox::StringView path) noexcept; + ox::Error setPalette(ox::StringViewCR path) noexcept; void setPalettePage(size_t pg) noexcept; @@ -128,7 +126,11 @@ class TileSheetEditorModel: public ox::SignalHandler { bool pixelSelected(std::size_t idx) const noexcept; private: - void getFillPixels(ox::Span pixels, ox::Point const&pt, int oldColor) const noexcept; + void getFillPixels( + TileSheet::SubSheet const&activeSubSheet, + ox::Span pixels, + ox::Point const&pt, + int oldColor) const noexcept; void pushCommand(studio::UndoCommand *cmd) noexcept; diff --git a/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditorview.cpp b/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditorview.cpp index 0cf1d87..0ac18ae 100644 --- a/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditorview.cpp +++ b/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditorview.cpp @@ -56,8 +56,10 @@ void TileSheetEditorView::scrollH(ox::Vec2 const&paneSz, float wheelh) noexcept } void TileSheetEditorView::insertTile(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept { - auto const pt = clickPoint(paneSize, clickPos); + auto pt = clickPoint(paneSize, clickPos); auto const&s = m_model.activeSubSheet(); + pt.x = ox::min(pt.x, s.columns * TileWidth - 1); + pt.y = ox::min(pt.y, s.rows * TileHeight - 1); auto const tileIdx = ptToIdx(pt, s.columns) / PixelsPerTile; m_model.insertTiles(m_model.activeSubSheetIdx(), tileIdx, 1); } diff --git a/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheetpixelgrid.cpp b/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheetpixelgrid.cpp index e38cb40..e84b6f5 100644 --- a/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheetpixelgrid.cpp +++ b/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheetpixelgrid.cpp @@ -80,6 +80,11 @@ void TileSheetGrid::setBufferObject( } void TileSheetGrid::setBufferObjects(ox::Vec2 const&paneSize, TileSheet::SubSheet const&subsheet) noexcept { + if (subsheet.columns < 1 || subsheet.rows < 1) { + m_bufferSet.elements.clear(); + m_bufferSet.vertices.clear(); + return; + } auto const pixSize = pixelSize(paneSize); auto const set = [&](std::size_t i, ox::Point pt1, ox::Point pt2, Color32 c) { auto const vbo = &m_bufferSet.vertices[i * VertexVboLength]; diff --git a/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheetpixels.cpp b/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheetpixels.cpp index b97e968..fc9551f 100644 --- a/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheetpixels.cpp +++ b/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheetpixels.cpp @@ -51,7 +51,7 @@ ox::Error TileSheetPixels::buildShader() noexcept { return glutils::buildShaderProgram(s_programSrc).moveTo(m_shader); } -void TileSheetPixels::draw(bool update, ox::Vec2 const&scroll) noexcept { +void TileSheetPixels::draw(bool const update, ox::Vec2 const&scroll) noexcept { glUseProgram(m_shader); glBindVertexArray(m_bufferSet.vao); if (update) { @@ -117,6 +117,11 @@ void TileSheetPixels::setPixelBufferObject( void TileSheetPixels::setBufferObjects(ox::Vec2 const&paneSize) noexcept { // set buffer lengths auto const&subSheet = m_model.activeSubSheet(); + if (subSheet.columns < 1 || subSheet.rows < 1) { + m_bufferSet.vertices.clear(); + m_bufferSet.elements.clear(); + return; + } auto const&pal = m_model.pal(); auto const width = subSheet.columns * TileWidth; auto const height = subSheet.rows * TileHeight; diff --git a/src/olympic/keel/include/keel/context.hpp b/src/olympic/keel/include/keel/context.hpp index a64fd61..b128bbf 100644 --- a/src/olympic/keel/include/keel/context.hpp +++ b/src/olympic/keel/include/keel/context.hpp @@ -12,7 +12,7 @@ namespace keel { class Context; -using PackTransform = ox::Result(*)(Context&, ox::Buffer &clawData, ox::StringView); +using PackTransform = ox::Result(*)(Context&, ox::Buffer &clawData, ox::StringViewCR); class Context { public: diff --git a/src/olympic/keel/include/keel/media.hpp b/src/olympic/keel/include/keel/media.hpp index 7accfae..069e202 100644 --- a/src/olympic/keel/include/keel/media.hpp +++ b/src/olympic/keel/include/keel/media.hpp @@ -70,7 +70,7 @@ constexpr auto makeLoader(Context &ctx) { if (err != ox::Error_ClawTypeVersionMismatch && err != ox::Error_ClawTypeMismatch) { return err; } - OX_RETURN_ERROR(convert(ctx, buff, &obj)); + OX_RETURN_ERROR(convert(ctx, buff, obj)); } return std::move(obj); }; diff --git a/src/olympic/keel/include/keel/pack.hpp b/src/olympic/keel/include/keel/pack.hpp index 417e51a..1bf5e21 100644 --- a/src/olympic/keel/include/keel/pack.hpp +++ b/src/olympic/keel/include/keel/pack.hpp @@ -125,7 +125,9 @@ ox::Error preloadObj( 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); + auto const&pbufSz = pl.buff().size(); + oxOutf("preloaded {} as a {} @ {} to {} / {}, total size: {}\n", + path, obj.type()->typeName, a, a + size, pbufSz - 1, pbufSz - a); } else { // strip the Claw header (it is not needed after preloading) and write back out to dest fs OX_RETURN_ERROR(ox::writeMC(obj).moveTo(buff)); diff --git a/src/olympic/keel/include/keel/typeconv.hpp b/src/olympic/keel/include/keel/typeconv.hpp index 7041e25..3d9ea00 100644 --- a/src/olympic/keel/include/keel/typeconv.hpp +++ b/src/olympic/keel/include/keel/typeconv.hpp @@ -16,10 +16,43 @@ namespace keel { class Wrap { public: virtual ~Wrap() = default; + [[nodiscard]] + virtual ox::CStringView typeName() const noexcept = 0; + [[nodiscard]] + virtual int typeVersion() const noexcept = 0; }; template -class WrapInline: public Wrap { +class WrapT: public Wrap { + public: + [[nodiscard]] + virtual constexpr T &obj() noexcept = 0; +}; + +template +class WrapRef final: public WrapT { + private: + T &m_obj; + + public: + constexpr explicit WrapRef(T &obj): m_obj{obj} {} + + ox::CStringView typeName() const noexcept override { + return ox::ModelTypeName_v; + } + + int typeVersion() const noexcept override { + return ox::ModelTypeVersion_v; + } + + constexpr T &obj() noexcept override { + return m_obj; + } + +}; + +template +class WrapInline final: public WrapT { private: T m_obj; @@ -30,8 +63,15 @@ class WrapInline: public Wrap { constexpr explicit WrapInline(Args &&...args): m_obj(ox::forward(args)...) { } - [[nodiscard]] - constexpr T &obj() noexcept { + ox::CStringView typeName() const noexcept override { + return ox::ModelTypeName_v; + } + + int typeVersion() const noexcept override { + return ox::ModelTypeVersion_v; + } + + constexpr T &obj() noexcept override { return m_obj; } @@ -44,7 +84,7 @@ constexpr ox::UPtr makeWrap(Args &&...args) noexcept { template constexpr T &wrapCast(Wrap &ptr) noexcept { - return static_cast&>(ptr).obj(); + return static_cast&>(ptr).obj(); } class BaseConverter { @@ -70,10 +110,10 @@ class BaseConverter { [[nodiscard]] constexpr bool matches( - ox::StringViewCR srcTypeName, int srcTypeVersion, - ox::StringViewCR dstTypeName, int dstTypeVersion) const noexcept { + ox::StringViewCR srcTypeName, int const srcTypeVersion, + ox::StringViewCR dstTypeName, int const dstTypeVersion) const noexcept { return srcMatches(srcTypeName, srcTypeVersion) - && dstMatches(dstTypeName, dstTypeVersion); + && dstMatches(dstTypeName, dstTypeVersion); } }; @@ -109,17 +149,17 @@ class Converter: public BaseConverter { ox::Result> convertPtrToPtr( keel::Context &ctx, Wrap &src) const noexcept final { - auto dst = makeWrap(); - OX_RETURN_ERROR(convert(ctx, wrapCast(src), wrapCast(*dst))); - return {std::move(dst)}; + ox::Result> dst{makeWrap()}; + OX_RETURN_ERROR(convert(ctx, wrapCast(src), wrapCast(*dst.value))); + return dst; } ox::Result> convertBuffToPtr( keel::Context &ctx, ox::BufferView const&srcBuff) const noexcept final { OX_REQUIRE_M(src, readAsset(srcBuff)); - auto dst = makeWrap(); - OX_RETURN_ERROR(convert(ctx, src, wrapCast(*dst))); - return {std::move(dst)}; + ox::Result> dst{makeWrap()}; + OX_RETURN_ERROR(convert(ctx, src, wrapCast(*dst.value))); + return dst; } protected: @@ -133,34 +173,57 @@ ox::Result> convert( ox::StringViewCR dstTypeName, int dstTypeVersion) noexcept; +ox::Result> convert( + keel::Context &ctx, + Wrap &src, + ox::StringViewCR dstTypeName, + int dstTypeVersion) noexcept; + +ox::Result> convert( + keel::Context &ctx, + auto &src, + ox::StringViewCR dstTypeName, + int const dstTypeVersion) noexcept { + return convert(ctx, WrapRef{src}, dstTypeName, dstTypeVersion); +} + template -ox::Result convert(keel::Context &ctx, ox::BufferView const&srcBuffer) noexcept { - static constexpr auto DstTypeName = ox::requireModelTypeName(); - static constexpr auto DstTypeVersion = ox::requireModelTypeVersion(); - OX_REQUIRE(out, convert(ctx, srcBuffer, DstTypeName, DstTypeVersion)); +ox::Result convertObjToObj( + keel::Context &ctx, + auto &src) noexcept { + OX_REQUIRE_M(out, convert(ctx, WrapRef{src}, ox::ModelTypeName_v, ox::ModelTypeVersion_v)); + return std::move(wrapCast(*out)); +} + +template +ox::Result convert(keel::Context &ctx, ox::BufferView const&src) noexcept { + OX_REQUIRE(out, convert(ctx, src, ox::ModelTypeName_v, ox::ModelTypeVersion_v)); return std::move(wrapCast(out)); } template -ox::Error convert(keel::Context &ctx, ox::BufferView const&buff, DstType *outObj) noexcept { - static constexpr auto DstTypeName = ox::requireModelTypeName(); - static constexpr auto DstTypeVersion = ox::requireModelTypeVersion(); - OX_REQUIRE(outPtr, convert(ctx, buff, DstTypeName, DstTypeVersion)); - *outObj = std::move(wrapCast(*outPtr)); +ox::Error convert(keel::Context &ctx, ox::BufferView const&buff, DstType &outObj) noexcept { + OX_REQUIRE(out, convert(ctx, buff, ox::ModelTypeName_v, ox::ModelTypeVersion_v)); + outObj = std::move(wrapCast(*out)); + return {}; +} + +template +ox::Error convertObjToObj(keel::Context &ctx, auto &src, DstType &outObj) noexcept { + OX_REQUIRE(outPtr, convert(ctx, src, ox::ModelTypeName_v, ox::ModelTypeVersion_v)); + outObj = std::move(wrapCast(*outPtr)); return {}; } template ox::Result convertBuffToBuff( - keel::Context &ctx, ox::BufferView const&srcBuffer, ox::ClawFormat fmt) noexcept { - static constexpr auto DstTypeName = ox::requireModelTypeName(); - static constexpr auto DstTypeVersion = ox::requireModelTypeVersion(); - OX_REQUIRE(out, convert(ctx, srcBuffer, DstTypeName, DstTypeVersion)); + keel::Context &ctx, ox::BufferView const&src, ox::ClawFormat const fmt) noexcept { + OX_REQUIRE(out, convert(ctx, src, ox::ModelTypeName_v, ox::ModelTypeVersion_v)); return ox::writeClaw(wrapCast(*out), fmt); } template -ox::Result transformRule(keel::Context &ctx, ox::Buffer &buff, ox::StringView typeId) noexcept { +ox::Result transformRule(keel::Context &ctx, ox::Buffer &buff, ox::StringViewCR typeId) noexcept { if (typeId == ox::ModelTypeId_v) { OX_RETURN_ERROR(keel::convertBuffToBuff(ctx, buff, fmt).moveTo(buff)); return true; @@ -168,5 +231,4 @@ ox::Result transformRule(keel::Context &ctx, ox::Buffer &buff, ox::StringV return false; }; - } diff --git a/src/olympic/keel/src/media.cpp b/src/olympic/keel/src/media.cpp index ff1b708..e59fa5e 100644 --- a/src/olympic/keel/src/media.cpp +++ b/src/olympic/keel/src/media.cpp @@ -154,13 +154,12 @@ ox::Result uuidToPath(Context &ctx, ox::UUID const&uuid) noexce } 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); + auto const [uuid, uuidErr] = getUuid(ctx, assetId); if (!uuidErr) { - return ctx.assetManager.reloadAsset(uuidStr); + return ctx.assetManager.reloadAsset(uuid.toString()); } else { return ctx.assetManager.reloadAsset(assetId); } diff --git a/src/olympic/keel/src/typeconv.cpp b/src/olympic/keel/src/typeconv.cpp index aeb598b..531e737 100644 --- a/src/olympic/keel/src/typeconv.cpp +++ b/src/olympic/keel/src/typeconv.cpp @@ -10,30 +10,38 @@ namespace keel { static ox::Result findConverter( ox::SpanView const&converters, ox::StringViewCR srcTypeName, - int srcTypeVersion, + int const srcTypeVersion, ox::StringViewCR dstTypeName, - int dstTypeVersion) noexcept { + int const dstTypeVersion) noexcept { for (auto const&c : converters) { if (c->matches(srcTypeName, srcTypeVersion, dstTypeName, dstTypeVersion)) { return c; } } - return ox::Error(1, "Could not find converter"); + return ox::Error{1, "Could not find converter"}; }; +static ox::Result> convert(BaseConverter const&c, Context &ctx, ox::BufferView const&src) noexcept { + return c.convertBuffToPtr(ctx, src); +} + +static ox::Result> convert(BaseConverter const&c, Context &ctx, Wrap &src) noexcept { + return c.convertPtrToPtr(ctx, src); +} + static ox::Result> convert( - keel::Context &ctx, + Context &ctx, ox::SpanView const&converters, - ox::BufferView const&srcBuffer, + auto &src, ox::StringViewCR srcTypeName, - int srcTypeVersion, + int const srcTypeVersion, ox::StringViewCR dstTypeName, - int dstTypeVersion) noexcept { + int const dstTypeVersion) noexcept { // look for direct converter - auto [c, err] = findConverter( + auto const [c, err] = findConverter( converters, srcTypeName, srcTypeVersion, dstTypeName, dstTypeVersion); if (!err) { - return c->convertBuffToPtr(ctx, srcBuffer); + return convert(*c, ctx, src); } // try to chain multiple converters for (auto const&subConverter : converters) { @@ -41,20 +49,20 @@ static ox::Result> convert( continue; } const auto [intermediate, chainErr] = - convert(ctx, converters, srcBuffer, srcTypeName, srcTypeVersion, + convert(ctx, converters, src, srcTypeName, srcTypeVersion, subConverter->srcTypeName(), subConverter->srcTypeVersion()); if (!chainErr) { return subConverter->convertPtrToPtr(ctx, *intermediate); } } - return ox::Error(1, "Could not convert between types"); + return ox::Error{1, "Could not convert between types"}; } ox::Result> convert( - keel::Context &ctx, + Context &ctx, ox::BufferView const&srcBuffer, ox::StringViewCR dstTypeName, - int dstTypeVersion) noexcept { + int const dstTypeVersion) noexcept { OX_REQUIRE(hdr, readAssetHeader(srcBuffer)); return convert( ctx, @@ -66,4 +74,19 @@ ox::Result> convert( dstTypeVersion); } +ox::Result> convert( + Context &ctx, + Wrap &src, + ox::StringViewCR dstTypeName, + int const dstTypeVersion) noexcept { + return convert( + ctx, + converters(ctx), + src, + src.typeName(), + src.typeVersion(), + dstTypeName, + dstTypeVersion); +} + } diff --git a/src/olympic/studio/applib/src/newproject.cpp b/src/olympic/studio/applib/src/newproject.cpp index a3f0099..6a0cdbe 100644 --- a/src/olympic/studio/applib/src/newproject.cpp +++ b/src/olympic/studio/applib/src/newproject.cpp @@ -50,7 +50,7 @@ void NewProject::draw(studio::StudioContext &ctx) noexcept { void NewProject::drawNewProjectName(studio::StudioContext &sctx) noexcept { drawWindow(sctx.tctx, &m_open, [this, &sctx] { - ImGui::InputText("Name", m_projectName.data(), m_projectName.cap()); + ig::InputText("Name", m_projectName); ImGui::Text("Path: %s", m_projectPath.c_str()); if (ImGui::Button("Browse")) { oxLogError(studio::chooseDirectory().moveTo(m_projectPath)); diff --git a/src/olympic/studio/applib/src/studioapp.cpp b/src/olympic/studio/applib/src/studioapp.cpp index 1fbea6e..7ae49c5 100644 --- a/src/olympic/studio/applib/src/studioapp.cpp +++ b/src/olympic/studio/applib/src/studioapp.cpp @@ -18,9 +18,9 @@ namespace studio { -static ox::Vector modules; +static ox::Vector modules; -void registerModule(studio::Module const*mod) noexcept { +void registerModule(Module const*mod) noexcept { if (mod) { modules.emplace_back(mod); } @@ -45,25 +45,25 @@ OX_MODEL_END() StudioUI::StudioUI(turbine::Context &ctx, ox::StringParam projectDataDir) noexcept: m_sctx(*this, ctx), - m_ctx(ctx), + m_tctx(ctx), m_projectDataDir(std::move(projectDataDir)), - m_projectExplorer(m_ctx), + m_projectExplorer(m_tctx), m_newProject(m_projectDataDir), - m_aboutPopup(m_ctx) { - turbine::setApplicationData(m_ctx, &m_sctx); + m_aboutPopup(m_tctx) { + turbine::setApplicationData(m_tctx, &m_sctx); m_projectExplorer.fileChosen.connect(this, &StudioUI::openFile); m_newProject.finished.connect(this, &StudioUI::createOpenProject); m_newMenu.finished.connect(this, &StudioUI::openFile); ImGui::GetIO().IniFilename = nullptr; loadModules(); // open project and files - auto const [config, err] = studio::readConfig(keelCtx(m_ctx)); + auto const [config, err] = studio::readConfig(keelCtx(m_tctx)); m_showProjectExplorer = config.showProjectExplorer; if (!err) { auto const openProjErr = openProjectPath(config.projectPath); if (!openProjErr) { for (auto const&f: config.openFiles) { - auto openFileErr = openFileActiveTab(f, config.activeTabItemName == f); + auto const openFileErr = openFileActiveTab(f, config.activeTabItemName == f); if (openFileErr) { oxErrorf("\nCould not open editor for file:\n\t{}\nReason:\n\t{}\n", f, toStr(openFileErr)); continue; @@ -82,8 +82,8 @@ StudioUI::StudioUI(turbine::Context &ctx, ox::StringParam projectDataDir) noexce } } -void StudioUI::handleKeyEvent(turbine::Key key, bool down) noexcept { - for (auto p : m_popups) { +void StudioUI::handleKeyEvent(turbine::Key const key, bool const down) noexcept { + for (auto const p : m_popups) { if (p->isOpen()) { if (key == turbine::Key::Escape) { p->close(); @@ -118,13 +118,13 @@ void StudioUI::draw() noexcept { for (auto &w: m_widgets) { w->draw(m_sctx); } - for (auto p: m_popups) { + for (auto const p: m_popups) { p->draw(m_sctx); } } ImGui::End(); handleKeyInput(); - m_taskRunner.update(m_ctx); + m_taskRunner.update(m_tctx); } void StudioUI::drawMenu() noexcept { @@ -143,7 +143,7 @@ void StudioUI::drawMenu() noexcept { m_activeEditor->save(); } if (ImGui::MenuItem("Quit", "Ctrl+Q")) { - turbine::requestShutdown(m_ctx); + turbine::requestShutdown(m_tctx); } ImGui::EndMenu(); } @@ -168,7 +168,7 @@ void StudioUI::drawMenu() noexcept { ImGui::EndMenu(); } if (ImGui::BeginMenu("View")) { - if (ImGui::MenuItem("Project Explorer", "Ctrl+1", m_showProjectExplorer)) { + if (ImGui::MenuItem("Project Explorer", "Ctrl+Shift+1", m_showProjectExplorer)) { toggleProjectExplorer(); } ImGui::EndMenu(); @@ -204,9 +204,10 @@ void StudioUI::drawTabs() noexcept { if (ImGui::BeginTabItem(e->itemDisplayName().c_str(), &open, flags)) { if (m_activeEditor != e.get()) [[unlikely]] { m_activeEditor = e.get(); - studio::editConfig(keelCtx(m_ctx), [&](StudioConfig &config) { + studio::editConfig(keelCtx(m_tctx), [&](StudioConfig &config) { config.activeTabItemName = m_activeEditor->itemPath(); }); + turbine::setRefreshWithin(m_tctx, 0); } else [[likely]] { if (m_activeEditorUpdatePending == e.get()) [[unlikely]] { m_activeEditorUpdatePending = nullptr; @@ -237,13 +238,13 @@ void StudioUI::drawTabs() noexcept { } } -void StudioUI::loadEditorMaker(studio::EditorMaker const&editorMaker) noexcept { +void StudioUI::loadEditorMaker(EditorMaker const&editorMaker) noexcept { for (auto const&ext : editorMaker.fileTypes) { m_editorMakers[ext] = editorMaker.make; } } -void StudioUI::loadModule(studio::Module const&mod) noexcept { +void StudioUI::loadModule(Module const&mod) noexcept { for (auto const&editorMaker : mod.editors(m_sctx)) { loadEditorMaker(editorMaker); } @@ -260,20 +261,20 @@ void StudioUI::loadModules() noexcept { void StudioUI::toggleProjectExplorer() noexcept { m_showProjectExplorer = !m_showProjectExplorer; - studio::editConfig(keelCtx(m_ctx), [&](StudioConfig &config) { + editConfig(keelCtx(m_tctx), [&](StudioConfig &config) { config.showProjectExplorer = m_showProjectExplorer; }); } void StudioUI::redo() noexcept { - auto undoStack = m_activeEditor ? m_activeEditor->undoStack() : nullptr; + auto const undoStack = m_activeEditor ? m_activeEditor->undoStack() : nullptr; if (undoStack && undoStack->canRedo()) { oxLogError(m_activeEditor->undoStack()->redo()); } } void StudioUI::undo() noexcept { - auto undoStack = m_activeEditor ? m_activeEditor->undoStack() : nullptr; + auto const undoStack = m_activeEditor ? m_activeEditor->undoStack() : nullptr; if (undoStack && undoStack->canUndo()) { oxLogError(m_activeEditor->undoStack()->undo()); } @@ -292,7 +293,7 @@ void StudioUI::handleKeyInput() noexcept { m_activeEditor->copy(); } } else if (ImGui::IsKeyPressed(ImGuiKey_N)) { - if (turbine::buttonDown(m_ctx, turbine::Key::Mod_Shift)) { + if (turbine::buttonDown(m_tctx, turbine::Key::Mod_Shift)) { m_newProject.open(); } else { m_newMenu.open(); @@ -302,7 +303,7 @@ void StudioUI::handleKeyInput() noexcept { } else if (ImGui::IsKeyPressed(ImGuiKey_S)) { save(); } else if (ImGui::IsKeyPressed(ImGuiKey_Q)) { - turbine::requestShutdown(m_ctx); + turbine::requestShutdown(m_tctx); } else if (ImGui::IsKeyPressed(ImGuiKey_V)) { if (m_activeEditor && m_activeEditor->pasteEnabled()) { m_activeEditor->paste(); @@ -317,8 +318,22 @@ void StudioUI::handleKeyInput() noexcept { } else if (ImGui::IsKeyPressed(ImGuiKey_Z)) { auto const undoStack = m_activeEditor ? m_activeEditor->undoStack() : nullptr; if (undoStack) { oxLogError(undoStack->undo()); } - } else if (ImGui::IsKeyPressed(ImGuiKey_1)) { + } else if (ImGui::IsKeyPressed(ImGuiKey_1) && ImGui::IsKeyDown(ImGuiKey_ModShift)) { toggleProjectExplorer(); + } else { + if (!m_editors.empty()) { + auto const range = ox::min(9u, m_editors.size()) - 1; + for (auto i = 0u; i < 9; ++i) { + if (ImGui::IsKeyPressed(static_cast(static_cast(ImGuiKey_1) + i))) { + m_activeEditor = m_editors[i < m_editors.size() ? i : range].get(); + m_activeEditorUpdatePending = m_activeEditor; + } + } + if (ImGui::IsKeyPressed(ImGuiKey_0)) { + m_activeEditor = m_editors[10 < m_editors.size() ? 10 : range].get(); + m_activeEditorUpdatePending = m_activeEditor; + } + } } } } @@ -333,18 +348,18 @@ ox::Error StudioUI::createOpenProject(ox::StringViewCR path) noexcept { ox::Error StudioUI::openProjectPath(ox::StringParam path) noexcept { OX_REQUIRE_M(fs, keel::loadRomFs(path.view())); - OX_RETURN_ERROR(keel::setRomFs(keelCtx(m_ctx), std::move(fs))); + OX_RETURN_ERROR(keel::setRomFs(keelCtx(m_tctx), std::move(fs))); OX_RETURN_ERROR( - ox::make_unique_catch(keelCtx(m_ctx), std::move(path), m_projectDataDir) + ox::make_unique_catch(keelCtx(m_tctx), std::move(path), m_projectDataDir) .moveTo(m_project)); - auto const sctx = applicationData(m_ctx); + auto const sctx = applicationData(m_tctx); sctx->project = m_project.get(); - turbine::setWindowTitle(m_ctx, ox::sfmt("{} - {}", keelCtx(m_ctx).appName, m_project->projectPath())); + turbine::setWindowTitle(m_tctx, ox::sfmt("{} - {}", keelCtx(m_tctx).appName, m_project->projectPath())); m_project->fileAdded.connect(&m_projectExplorer, &ProjectExplorer::refreshProjectTreeModel); m_project->fileDeleted.connect(&m_projectExplorer, &ProjectExplorer::refreshProjectTreeModel); m_openFiles.clear(); m_editors.clear(); - studio::editConfig(keelCtx(m_ctx), [&](StudioConfig &config) { + studio::editConfig(keelCtx(m_tctx), [&](StudioConfig &config) { config.projectPath = ox::String(m_project->projectPath()); config.openFiles.clear(); }); @@ -355,7 +370,7 @@ ox::Error StudioUI::openFile(ox::StringViewCR path) noexcept { return openFileActiveTab(path, true); } -ox::Error StudioUI::openFileActiveTab(ox::StringViewCR path, bool makeActiveTab) noexcept { +ox::Error StudioUI::openFileActiveTab(ox::StringViewCR path, bool const makeActiveTab) noexcept { if (!m_project) { return ox::Error(1, "No project open to open a file from"); } @@ -369,7 +384,7 @@ ox::Error StudioUI::openFileActiveTab(ox::StringViewCR path, bool makeActiveTab) } return {}; } - OX_REQUIRE(ext, studio::fileExt(path)); + OX_REQUIRE(ext, fileExt(path)); // create Editor BaseEditor *editor = nullptr; auto const err = m_editorMakers.contains(ext) ? @@ -391,7 +406,7 @@ ox::Error StudioUI::openFileActiveTab(ox::StringViewCR path, bool makeActiveTab) m_activeEditorUpdatePending = editor; } // save to config - studio::editConfig(keelCtx(m_ctx), [&path](StudioConfig &config) { + studio::editConfig(keelCtx(m_tctx), [&path](StudioConfig &config) { if (!config.openFiles.contains(path)) { config.openFiles.emplace_back(path); } @@ -405,7 +420,7 @@ ox::Error StudioUI::closeFile(ox::StringViewCR path) noexcept { } std::ignore = m_openFiles.erase(std::remove(m_openFiles.begin(), m_openFiles.end(), path)); // save to config - studio::editConfig(keelCtx(m_ctx), [&](StudioConfig &config) { + studio::editConfig(keelCtx(m_tctx), [&](StudioConfig &config) { std::ignore = config.openFiles.erase(std::remove(config.openFiles.begin(), config.openFiles.end(), path)); }); return {}; diff --git a/src/olympic/studio/applib/src/studioapp.hpp b/src/olympic/studio/applib/src/studioapp.hpp index 92cefaa..6490786 100644 --- a/src/olympic/studio/applib/src/studioapp.hpp +++ b/src/olympic/studio/applib/src/studioapp.hpp @@ -24,23 +24,23 @@ class StudioUI: public ox::SignalHandler { friend class StudioUIDrawer; private: - studio::StudioContext m_sctx; - turbine::Context &m_ctx; + StudioContext m_sctx; + turbine::Context &m_tctx; ox::String m_projectDataDir; - ox::UPtr m_project; - studio::TaskRunner m_taskRunner; - ox::Vector> m_editors; - ox::Vector> m_widgets; - ox::HashMap m_editorMakers; + ox::UPtr m_project; + TaskRunner m_taskRunner; + ox::Vector> m_editors; + ox::Vector> m_widgets; + ox::HashMap m_editorMakers; ProjectExplorer m_projectExplorer; ox::Vector m_openFiles; - studio::BaseEditor *m_activeEditorOnLastDraw = nullptr; - studio::BaseEditor *m_activeEditor = nullptr; - studio::BaseEditor *m_activeEditorUpdatePending = nullptr; + BaseEditor *m_activeEditorOnLastDraw = nullptr; + BaseEditor *m_activeEditor = nullptr; + BaseEditor *m_activeEditorUpdatePending = nullptr; NewMenu m_newMenu; NewProject m_newProject; AboutPopup m_aboutPopup; - ox::Array const m_popups = { + ox::Array const m_popups = { &m_newMenu, &m_newProject, &m_aboutPopup @@ -53,7 +53,7 @@ class StudioUI: public ox::SignalHandler { void handleKeyEvent(turbine::Key, bool down) noexcept; [[nodiscard]] - constexpr studio::Project *project() noexcept { + constexpr Project *project() noexcept { return m_project.get(); } @@ -67,9 +67,9 @@ class StudioUI: public ox::SignalHandler { void drawTabs() noexcept; - void loadEditorMaker(studio::EditorMaker const&editorMaker) noexcept; + void loadEditorMaker(EditorMaker const&editorMaker) noexcept; - void loadModule(studio::Module const&mod) noexcept; + void loadModule(Module const&mod) noexcept; void loadModules() noexcept;