Squashed 'deps/nostalgia/' changes from eed115b2..7d9f363b

7d9f363b [nostalgia/core/studio] Show color names in TileSheetEditor
ababc2a7 [nostalgia/core/studio] Add Name to colors table in PaletteEditor
bfd4bc3c [nostalgia/core] Revise Palette system, give TileSheetEditor a config file
8826d51e [studio] Add configio.hpp to studio.hpp
c021e5e7 [ox/oc] Fix OC not dealing with certain int types properly
7d8a8e0e [keel] Cleanup
95a69b72 [ox/std] Fix String::c_str to always retrun a valid C str
e4c38660 [nostalgia/core] Fix subsheet resize not to read garbage
67cf3ae8 [ox/std] Ensure ox::String always has at least a null terminator
2761f23d [nostalgia/developer-handbook] Update serialization notes
6c170d31 [nostalgia/developer-handbook] Update serialization notes
d20bfc77 [nostalgia/developer-handbook] Update serialization notes
07ecbde1 [nostalgia/developer-handbook] Update notes on error handling
fbe2fcd3 [nostalgia/developer-handbook] Update notes on error handling
a8bb99b6 [turbine] Remove ClipboardObject::typeMatches
667dd21a [turbine] Make ClipboardObject::typId return a StringView
5d89370c [turbine] Rework getClipboardObject to use ox::ModelTypeId_v
b968ec8a [keel] Remove redundant move
ec45ffb7 [studio] Fix build error
97dc0274 [nostalgia/core/studio] Add acceptsClipboardPayload to TileSheetEditor
a138f60f [studio] Add acceptsClipboardPayload to Editor
791d1950 [turbine] Make ClipboardObject use ox::ModelTypeId_v for typeId
78eb8fca [keel] Cleanup pack logging
0b8051b6 [ox/preloader] Fix alignment issue
5a426829 [nostalgia/core/studio] Cleanup TileSheet selection, fix copy/paste bug
9d2fe0e8 [studio] Add size function to Selection
f1894699 [keel] Remove setAsset
27b38ed2 [keel,studio] Fix hotloading for files that get loaded as multiple types
2bb7c514 [studio/modlib] Fix type desc writing logic inversion
5177cfb0 [studio/modlib] Make Project::mkdir only mkdir if dir does not exist
f9a14433 [studio/modlib] Add variant of ComboBox that takes callback
e62426b0 [keel] Ensure consistent asset IDs in AssetManager
af634bd4 [ox/fs] Add FileSystem::exists
49b859ec [studio/modlib] Give Selection constructors
19a41201 [studio/modlib] Make iterateSelection return errors properly
f69b8afa [nostalgia] Remove use of deleted function
9c98b5e2 [studio/modlib] Remove color.hpp
1f87216d [nostalgia/core] Add applySelectionColor
94c59604 [nostalgia/core/opengl] Fix for Ox changes
8ee016c1 [studio/modlib] Add SelectionTracker
dc20c667 [ox/std] Add conversion functions for geo types, cleanup
407e5424 [ox/std] Remove SmallMap dtor, replace timing code with steady_clock
3b188696 [ox/claw] Remove enum type from ClawFormat
0fab6c7c [ox/preloader] Remove debug code
a72b865d [studio/modlib] Add function for exporting selection color
c0479604 [studio,nostalgia/studio] Make executing UndoCommands report errors
a1c89906 [nostalgia/studio] Make UndoCommand undo/redo return ox::Error
7fb0549c [nostalgia/core] Revert some auto formatting done by CLion...
37e65ab0 [nostalgia/core/studio] Fix Subsheet width to update properly
9105b1ec [ox/std] Fix Linux build
fbeb0815 [ox/model] Fix type params in buildTypeId
b882a47e [ox/std] Fix resize to set null terminator
660f2f56 [ox/std] Rework FileReader into StreamReader
aa83c2a6 [nostalgia/core/studio] Remove some unnecessary copying
4a2b1fd7 [studio,keel] Make fileChanged emit UUID as well as path, add uuidUrlToUuid
08f958fb [ox/std] Add IntegerRange_c
a651d45a [ox/std] Fix Vector insert functions
9e9f317c [studio] Make UndoCommand::mergeWith take a reference
f5a02ce9 [nostalgia/core/gba] Fix build
6971c310 [studio] Add NoChangeException
c47f48eb [keel] Add/cleanup UUID/path lookup functions
76771e7b [nostalgia/core] Add tileColumns and tileRows functions
f6a0ae20 [ox/std] Fix some Windows warnings
752c8c1d [glutils] Fix type conversion that happened on Windows
af3bff1a [glutils] Add FrameBuffer::sizef
87416e13 [ox/std] Make MallocaPtr call destructor
047b4396 [ox/std] Make Point and Size members int32_t
40b8da4d [studio/modlib] Cleanup
123c4125 [ox/std] Add SmallMap::pairs(), SmallMap model
963ec5d3 [ox/std] Add operator-> to SpanIterator
6df77a23 [glutils] Add size function to FrameBuffer
df412cf8 [ox/std] Add missing typenames
ae30ef36 Merge commit 'b66cef7127e97269fc6072a6f66ccc08990f6d2e'
095a1135 Merge commit 'f48824793cfce315971fe2e699ece198c7a79407'
ce1836ab Merge commit '1e041bd2ebfe5ace7bed3906faf60345aa98a8bc'
7d1641fa Merge commit '420fa96463f59c4a4a7cd66b16b0ad01ab0d55e6'
423212b2 [studio] Add missing include
60da1063 Merge commit 'bd416f82e25f1b710ab2b7890274571dd3fcd53d'
60d1996f [keel] Minor optimization

git-subtree-dir: deps/nostalgia
git-subtree-split: 7d9f363bfa7a2c64f5c4bcfd0b6686f3f5678119
This commit is contained in:
2024-05-30 23:14:19 -05:00
parent b66cef7127
commit 76b8b03a2c
90 changed files with 1633 additions and 832 deletions

View File

@ -13,6 +13,7 @@
#include <ox/std/error.hpp>
#include <ox/std/size.hpp>
#include <ox/std/string.hpp>
#include <ox/std/vec.hpp>
#include <ox/std/vector.hpp>
namespace glutils {
@ -137,6 +138,22 @@ struct FrameBuffer {
constexpr operator const GLuint&() const noexcept {
return fbo.id;
}
[[nodiscard]]
constexpr ox::Vec2 sizef() const noexcept {
return {
static_cast<float>(width),
static_cast<float>(height),
};
}
[[nodiscard]]
constexpr ox::Size size() const noexcept {
return {
width,
height,
};
}
};
class FrameBufferBind {

View File

@ -102,7 +102,7 @@ void setupShaderParams(
ox::Vector<ShaderVarSet> const&vars,
GLsizei vertexRowLen) noexcept {
// setup vars
for (auto lenWritten = 0LU; auto const&v : vars) {
for (size_t lenWritten = 0; auto const&v : vars) {
auto const attr = static_cast<GLuint>(glGetAttribLocation(shader, v.name.c_str()));
glEnableVertexAttribArray(attr);
glVertexAttribPointer(

View File

@ -10,7 +10,7 @@
namespace ox {
enum class ClawFormat: int {
enum class ClawFormat {
None,
Metal,
Organic,

View File

@ -99,6 +99,21 @@ class FileSystem {
Result<FileStat> stat(const FileAddress &addr) const noexcept;
[[nodiscard]]
inline bool exists(uint64_t inode) const noexcept {
return statInode(inode).ok();
}
[[nodiscard]]
inline bool exists(ox::StringView path) const noexcept {
return statPath(path).ok();
}
[[nodiscard]]
inline bool exists(FileAddress const&addr) const noexcept {
return stat(addr).ok();
}
[[nodiscard]]
virtual uint64_t spaceNeeded(uint64_t size) const noexcept = 0;

View File

@ -34,15 +34,16 @@ constexpr auto buildTypeId() noexcept {
return ox::sfmt("{};{}", name, version);
}
static constexpr auto buildTypeId(CRStringView name, int version,
const TypeParamPack &typeParams = {}) noexcept {
static constexpr auto buildTypeId(
CRStringView name, int version,
const TypeParamPack &typeParams = {}) noexcept {
String tp;
if (!typeParams.empty()) {
tp = "#";
for (const auto &p : typeParams) {
tp += p + ",";
}
tp.resize(tp.len() - 1);
tp.resize(tp.len());
tp += "#";
}
return ox::sfmt("{}{};{}", name, tp, version);

View File

@ -36,135 +36,6 @@ OrganicClawReader::OrganicClawReader(Json::Value json, int unionIdx) noexcept:
m_unionIdx(unionIdx) {
}
Error OrganicClawReader::field(const char *key, int8_t *val) noexcept {
auto err = OxError(0);
if (targetValid()) {
const auto &jv = value(key);
if (jv.empty()) {
*val = 0;
} else if (jv.isInt()) {
*val = static_cast<int8_t>(jv.asInt());
} else {
err = OxError(1, "Type mismatch");
}
}
++m_fieldIt;
return err;
}
Error OrganicClawReader::field(const char *key, int16_t *val) noexcept {
auto err = OxError(0);
if (targetValid()) {
const auto &jv = value(key);
if (jv.empty()) {
*val = 0;
} else if (jv.isInt()) {
*val = static_cast<int16_t>(jv.asInt());
} else {
err = OxError(1, "Type mismatch");
}
}
++m_fieldIt;
return err;
}
Error OrganicClawReader::field(const char *key, int32_t *val) noexcept {
auto err = OxError(0);
if (targetValid()) {
const auto &jv = value(key);
if (jv.empty()) {
*val = 0;
} else if (jv.isInt()) {
*val = static_cast<int32_t>(jv.asInt());
} else {
err = OxError(1, "Type mismatch");
}
}
++m_fieldIt;
return err;
}
Error OrganicClawReader::field(const char *key, int64_t *val) noexcept {
auto err = OxError(0);
if (targetValid()) {
const auto &jv = value(key);
if (jv.empty()) {
*val = 0;
} else if (jv.isInt() || jv.isInt64()) {
*val = static_cast<int64_t>(jv.asInt64());
} else {
err = OxError(1, "Type mismatch");
}
}
++m_fieldIt;
return err;
}
Error OrganicClawReader::field(const char *key, uint8_t *val) noexcept {
auto err = OxError(0);
if (targetValid()) {
const auto &jv = value(key);
if (jv.empty()) {
*val = 0;
} else if (jv.isUInt()) {
*val = static_cast<uint8_t>(jv.asUInt());
} else {
err = OxError(1, "Type mismatch");
}
}
++m_fieldIt;
return err;
}
Error OrganicClawReader::field(const char *key, uint16_t *val) noexcept {
auto err = OxError(0);
if (targetValid()) {
const auto &jv = value(key);
if (jv.empty()) {
*val = 0;
} else if (jv.isUInt()) {
*val = static_cast<uint16_t>(jv.asUInt());
} else {
err = OxError(1, "Type mismatch");
}
}
++m_fieldIt;
return err;
}
Error OrganicClawReader::field(const char *key, uint32_t *val) noexcept {
auto err = OxError(0);
if (targetValid()) {
const auto &jv = value(key);
if (jv.empty()) {
*val = 0;
} else if (jv.isUInt()) {
*val = static_cast<uint32_t>(jv.asUInt());
} else {
err = OxError(1, "Type mismatch");
}
}
++m_fieldIt;
return err;
}
Error OrganicClawReader::field(const char *key, uint64_t *val) noexcept {
auto err = OxError(0);
if (targetValid()) {
const auto &jv = value(key);
if (jv.empty()) {
*val = 0;
} else if (jv.isUInt() || jv.isUInt64()) {
*val = static_cast<uint64_t>(jv.asUInt64());
} else {
err = OxError(1, "Type mismatch");
}
}
++m_fieldIt;
return err;
}
Error OrganicClawReader::field(const char *key, bool *val) noexcept {
auto err = OxError(0);
if (targetValid()) {

View File

@ -41,16 +41,6 @@ class OrganicClawReader {
explicit OrganicClawReader(Json::Value json, int unionIdx = -1) noexcept;
Error field(const char *key, int8_t *val) noexcept;
Error field(const char *key, int16_t *val) noexcept;
Error field(const char *key, int32_t *val) noexcept;
Error field(const char *key, int64_t *val) noexcept;
Error field(const char *key, uint8_t *val) noexcept;
Error field(const char *key, uint16_t *val) noexcept;
Error field(const char *key, uint32_t *val) noexcept;
Error field(const char *key, uint64_t *val) noexcept;
Error field(const char *key, bool *val) noexcept;
// array handler
@ -144,28 +134,46 @@ class OrganicClawReader {
template<typename T>
Error OrganicClawReader::field(const char *key, T *val) noexcept {
auto err = OxError(0);
if constexpr(isVector_v<T>) {
const auto &srcVal = value(key);
const auto srcSize = srcVal.size();
oxReturnError(ox::resizeVector(*val, srcSize));
err = field(key, val->data(), val->size());
} else if constexpr(isArray_v<T>) {
const auto &srcVal = value(key);
const auto srcSize = srcVal.size();
if (srcSize > val->size()) {
err = OxError(1, "Input array is too long");
} else {
try {
if constexpr (is_integer_v<T>) {
if (targetValid()) {
auto const&jv = value(key);
auto const rightType = sizeof(T) == 8 ?
(ox::is_signed_v<T> ? jv.isInt64() : jv.isUInt64()) :
(ox::is_signed_v<T> ? jv.isInt() : jv.isUInt());
if (jv.empty()) {
*val = 0;
} else if (rightType) {
*val = static_cast<T>(jv.asUInt());
} else {
err = OxError(1, "Type mismatch");
}
}
} else if constexpr (isVector_v<T>) {
const auto&srcVal = value(key);
const auto srcSize = srcVal.size();
oxReturnError(ox::resizeVector(*val, srcSize));
err = field(key, val->data(), val->size());
} else if constexpr (isArray_v<T>) {
const auto&srcVal = value(key);
const auto srcSize = srcVal.size();
if (srcSize > val->size()) {
err = OxError(1, "Input array is too long");
} else {
err = field(key, val->data(), val->size());
}
} else if (targetValid()) {
const auto&jv = value(key);
if (jv.empty() || jv.isObject()) {
auto reader = child(key);
ModelHandlerInterface handler(&reader);
err = model(&handler, val);
} else {
err = OxError(1, "Type mismatch");
}
}
} else if (targetValid()) {
const auto &jv = value(key);
if (jv.empty() || jv.isObject()) {
auto reader = child(key);
ModelHandlerInterface handler(&reader);
err = model(&handler, val);
} else {
err = OxError(1, "Type mismatch");
}
} catch (Json::LogicError const&) {
err = OxError(1, "error reading JSON data");
}
++m_fieldIt;
return err;

View File

@ -209,7 +209,17 @@ Error OrganicClawWriter::field(const char *key, const T *val, std::size_t len) n
template<typename T>
Error OrganicClawWriter::field(const char *key, const T *val) noexcept {
if constexpr(isVector_v<T> || isArray_v<T>) {
if constexpr(is_integer_v<T>) {
if (targetValid() && (*val || m_json.isArray())) {
// the int type needs to be normalized because jsoncpp doesn't
// factor in every permutation unsigned long, etc.
if constexpr(ox::is_signed_v<T>) {
value(key) = static_cast<ox::Int<8 * sizeof(*val)>>(*val);
} else {
value(key) = static_cast<ox::Uint<8 * sizeof(*val)>>(*val);
}
}
} else if constexpr(isVector_v<T> || isArray_v<T>) {
return field(key, val->data(), val->size());
} else if (val && targetValid()) {
OrganicClawWriter w;

View File

@ -94,7 +94,7 @@ class Preloader: public ModelHandlerBase<Preloader<PlatSpec>, OpType::Reflect> {
}
template<typename U, bool force>
constexpr ox::Error field(CRStringView, const ox::UnionView<U, force> val) noexcept;
constexpr ox::Error field(CRStringView, ox::UnionView<U, force> val) noexcept;
template<typename T>
constexpr ox::Error field(CRStringView, const T *val) noexcept;
@ -135,6 +135,9 @@ class Preloader: public ModelHandlerBase<Preloader<PlatSpec>, OpType::Reflect> {
constexpr ox::Error fieldArray(CRStringView name, ox::ModelValueArray const*val) noexcept;
constexpr bool unionCheckAndIt() noexcept;
[[nodiscard]]
constexpr size_t calcPadding(size_t align) const noexcept;
};
template<typename PlatSpec>
@ -265,7 +268,7 @@ template<typename PlatSpec>
constexpr ox::Result<std::size_t> Preloader<PlatSpec>::startAlloc(size_t sz, size_t align) noexcept {
m_allocStack.emplace_back(static_cast<typename PlatSpec::PtrType>(m_writer.tellp()));
oxReturnError(m_writer.seekp(0, ox::ios_base::end));
const auto padding = m_writer.tellp() % align;
auto const padding = calcPadding(align);
oxRequireM(a, ox::allocate(m_writer, sz + padding));
a += padding;
oxReturnError(m_writer.seekp(a));
@ -278,7 +281,7 @@ constexpr ox::Result<std::size_t> Preloader<PlatSpec>::startAlloc(
std::size_t sz, size_t align, std::size_t restore) noexcept {
m_allocStack.emplace_back(restore, ox::ios_base::beg);
oxReturnError(m_writer.seekp(0, ox::ios_base::end));
const auto padding = m_writer.tellp() % align;
auto const padding = calcPadding(align);
oxRequireM(a, ox::allocate(m_writer, sz + padding));
a += padding;
oxReturnError(m_writer.seekp(a));
@ -358,7 +361,7 @@ constexpr ox::Error Preloader<PlatSpec>::fieldVector(
const auto sz = sizeOf<PlatSpec>(&(*val)[0]) * val->size();
const auto align = alignOf<PlatSpec>((*val)[0]);
oxReturnError(m_writer.seekp(0, ox::ios_base::end));
const auto padding = m_writer.tellp() % align;
auto const padding = calcPadding(align);
oxRequireM(p, ox::allocate(m_writer, sz + padding));
p += padding;
oxReturnError(m_writer.seekp(p));
@ -381,8 +384,6 @@ constexpr ox::Error Preloader<PlatSpec>::fieldVector(
template<typename PlatSpec>
constexpr ox::Error Preloader<PlatSpec>::fieldArray(CRStringView, ox::ModelValueArray const*val) noexcept {
oxDebugf("array size: {}", val->size());
oxDebugf("array sizeOf: {}", sizeOf<PlatSpec>(val));
oxReturnError(pad(&(*val)[0]));
for (auto const&v : *val) {
oxReturnError(this->interface()->field({}, &v));
@ -396,6 +397,12 @@ constexpr bool Preloader<PlatSpec>::unionCheckAndIt() noexcept {
return u.checkAndIterate();
}
template<typename PlatSpec>
constexpr size_t Preloader<PlatSpec>::calcPadding(size_t align) const noexcept {
auto const excess = m_writer.tellp() % align;
return (align * (excess != 0)) - excess;
}
template<typename PlatSpec, typename T>
constexpr ox::Error preload(Preloader<PlatSpec> *pl, ox::CommonPtrWith<T> auto *obj) noexcept {
oxReturnError(model(pl->interface(), obj));

View File

@ -96,6 +96,7 @@ install(
buildinfo.hpp
byteswap.hpp
concepts.hpp
conv.hpp
def.hpp
defer.hpp
defines.hpp

View File

@ -32,7 +32,7 @@ constexpr To bit_cast(const From &src) noexcept requires(sizeof(To) == sizeof(Fr
namespace ox {
template<typename To, typename From>
constexpr typename enable_if<sizeof(To) == sizeof(From), To>::type cbit_cast(From src) noexcept {
constexpr To cbit_cast(From src) noexcept requires(sizeof(To) == sizeof(From)) {
To dst = {};
ox::memcpy(&dst, &src, sizeof(src));
return dst;

View File

@ -8,6 +8,7 @@
#pragma once
#include "bit.hpp"
#include "typetraits.hpp"
namespace ox {
@ -29,4 +30,6 @@ concept OxString_c = isOxString_v<T>;
template<typename T>
concept Integral_c = ox::is_integral_v<T>;
template<typename T, size_t max>
concept IntegerRange_c = ox::is_integer_v<T> && ox::MaxValue<T> >= max;
}

45
deps/ox/src/ox/std/conv.hpp vendored Normal file
View File

@ -0,0 +1,45 @@
/*
* Copyright 2015 - 2024 gary@drinkingtea.net
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include "point.hpp"
#include "size.hpp"
#include "vec.hpp"
namespace ox {
constexpr Vec2::operator Point() const noexcept {
return {
static_cast<int32_t>(x),
static_cast<int32_t>(y),
};
}
constexpr Vec2::operator Size() const noexcept {
return {
static_cast<int32_t>(x),
static_cast<int32_t>(y),
};
}
constexpr Point::operator Vec2() const noexcept {
return {
static_cast<float>(x),
static_cast<float>(y),
};
}
constexpr Size::operator Vec2() const noexcept {
return {
static_cast<float>(width),
static_cast<float>(height),
};
}
}

View File

@ -128,6 +128,10 @@ struct SpanIterator {
return operator-=(1);
}
constexpr PtrType operator->() const noexcept {
return &m_t[m_offset];
}
constexpr RefType operator*() const noexcept {
return m_t[m_offset];
}

View File

@ -102,6 +102,7 @@ class MallocaPtr {
}
constexpr ~MallocaPtr() noexcept {
m_val->~T();
if (m_onHeap && m_val) {
delete[] reinterpret_cast<uint8_t*>(m_val);
}

View File

@ -17,8 +17,8 @@ class Point {
public:
static constexpr auto TypeName = "net.drinkingtea.ox.Point";
static constexpr auto TypeVersion = 1;
int x = 0;
int y = 0;
int32_t x = 0;
int32_t y = 0;
constexpr Point() noexcept = default;
@ -64,6 +64,7 @@ class Point {
constexpr bool operator!=(const Point&) const noexcept;
explicit constexpr operator class Vec2() const noexcept;
};
constexpr Point::Point(int x, int y) noexcept {

View File

@ -8,7 +8,7 @@
#ifdef OX_USE_STDLIB
#include <cstdio>
#include <istream>
#include "array.hpp"
#include "reader.hpp"
@ -16,41 +16,56 @@
namespace ox {
[[nodiscard]]
constexpr int sdMap(ox::ios_base::seekdir in) noexcept {
constexpr std::ios_base::seekdir sdMap(ox::ios_base::seekdir in) noexcept {
switch (in) {
case ox::ios_base::beg:
return SEEK_SET;
return std::ios_base::beg;
case ox::ios_base::end:
return SEEK_END;
return std::ios_base::end;
case ox::ios_base::cur:
return SEEK_CUR;
return std::ios_base::cur;
}
return -1;
return std::ios_base::beg;
}
ox::Result<char> FileReader::peek() const noexcept {
auto const c = fgetc(m_file);
auto const ok = c != EOF;
if (ok && ungetc(c, m_file)) [[unlikely]] {
return OxError(1, "Unable to unget character");
ox::Result<char> StreamReader::peek() const noexcept {
try {
char c{};
m_strm.get(c);
auto const ok = c != EOF;
if (ok && m_strm.unget()) [[unlikely]] {
return OxError(1, "Unable to unget character");
}
return {static_cast<char>(c), OxError(!ok, "File peek failed")};
} catch (std::exception const&) {
return OxError(1, "peek failed");
}
return {static_cast<char>(c), OxError(!ok, "File peek failed")};
}
ox::Result<std::size_t> FileReader::read(char *v, std::size_t cnt) noexcept {
return fread(v, 1, cnt, m_file);
ox::Result<std::size_t> StreamReader::read(char *v, std::size_t cnt) noexcept {
return static_cast<size_t>(m_strm.read(v, static_cast<std::streamsize>(cnt)).gcount());
}
ox::Error FileReader::seekg(std::size_t p) noexcept {
return OxError(fseek(m_file, static_cast<int64_t>(p), SEEK_CUR) != 0);
ox::Error StreamReader::seekg(std::size_t p) noexcept {
try {
m_strm.seekg(static_cast<long long int>(p), std::ios_base::cur);
} catch (std::exception const&) {
return OxError(1, "seekg failed");
}
return {};
}
ox::Error FileReader::seekg(int64_t p, ios_base::seekdir sd) noexcept {
return OxError(fseek(m_file, p, sdMap(sd)) != 0);
ox::Error StreamReader::seekg(int64_t p, ios_base::seekdir sd) noexcept {
try {
m_strm.seekg(p, sdMap(sd));
} catch (std::exception const&) {
return OxError(1, "seekg failed");
}
return {};
}
ox::Result<std::size_t> FileReader::tellg() noexcept {
const auto sz = ftell(m_file);
ox::Result<std::size_t> StreamReader::tellg() noexcept {
const auto sz = m_strm.tellg();
return {static_cast<std::size_t>(sz), OxError(sz == -1)};
}

View File

@ -9,7 +9,7 @@
#pragma once
#ifdef OX_USE_STDLIB
#include <cstdio>
#include <istream>
#endif
#include "concepts.hpp"
@ -65,11 +65,11 @@ class ReaderT: public Reader_v {
};
#ifdef OX_USE_STDLIB
class FileReader: public Reader_v {
class StreamReader: public Reader_v {
private:
FILE *m_file = nullptr;
std::istream &m_strm;
public:
constexpr explicit FileReader(FILE *file) noexcept: m_file(file) {}
constexpr explicit StreamReader(std::istream &stream) noexcept: m_strm(stream) {}
ox::Result<char> peek() const noexcept override;
ox::Result<std::size_t> read(char *v, std::size_t cnt) noexcept override;
ox::Error seekg(std::size_t p) noexcept override;

View File

@ -17,8 +17,8 @@ class Size {
public:
static constexpr auto TypeName = "net.drinkingtea.ox.Size";
static constexpr auto TypeVersion = 1;
int width = 0;
int height = 0;
int32_t width = 0;
int32_t height = 0;
constexpr Size() noexcept = default;
@ -64,6 +64,7 @@ class Size {
constexpr bool operator!=(const Size&) const noexcept;
explicit constexpr operator class Vec2() const noexcept;
};
constexpr Size::Size(int width, int height) noexcept {

View File

@ -8,7 +8,7 @@
#pragma once
#include "algorithm.hpp"
#include "def.hpp"
#include "hash.hpp"
#include "ignore.hpp"
#include "stringview.hpp"
@ -28,7 +28,7 @@ class SmallMap {
T value{};
};
private:
protected:
using PairVector = Vector<Pair, SmallSz>;
PairVector m_pairs;
@ -39,8 +39,6 @@ class SmallMap {
constexpr SmallMap(SmallMap &&other) noexcept;
constexpr ~SmallMap();
constexpr bool operator==(SmallMap const&other) const;
constexpr SmallMap &operator=(SmallMap const&other);
@ -79,6 +77,11 @@ class SmallMap {
[[nodiscard]]
constexpr Pair &get(size_t i) noexcept;
[[nodiscard]]
constexpr ox::SpanView<Pair> pairs() const noexcept {
return m_pairs;
}
constexpr void clear();
private:
@ -100,11 +103,6 @@ constexpr SmallMap<K, T, SmallSz>::SmallMap(SmallMap<K, T, SmallSz> &&other) noe
m_pairs = std::move(other.m_pairs);
}
template<typename K, typename T, size_t SmallSz>
constexpr SmallMap<K, T, SmallSz>::~SmallMap() {
clear();
}
template<typename K, typename T, size_t SmallSz>
constexpr bool SmallMap<K, T, SmallSz>::operator==(SmallMap const&other) const {
return m_pairs == other.m_pairs;
@ -204,12 +202,12 @@ constexpr T &SmallMap<K, T, SmallSz>::value(size_t i) noexcept {
}
template<typename K, typename T, size_t SmallSz>
constexpr SmallMap<K, T, SmallSz>::Pair const&SmallMap<K, T, SmallSz>::get(size_t i) const noexcept {
constexpr typename SmallMap<K, T, SmallSz>::Pair const&SmallMap<K, T, SmallSz>::get(size_t i) const noexcept {
return m_pairs[i];
}
template<typename K, typename T, size_t SmallSz>
constexpr SmallMap<K, T, SmallSz>::Pair &SmallMap<K, T, SmallSz>::get(size_t i) noexcept {
constexpr typename SmallMap<K, T, SmallSz>::Pair &SmallMap<K, T, SmallSz>::get(size_t i) noexcept {
return m_pairs[i];
}
@ -246,4 +244,12 @@ constexpr typename SmallMap<K, T, SmallSz>::Pair &SmallMap<K, T, SmallSz>::acces
return pairs.emplace_back();
}
template<typename T, typename K, typename V, size_t SmallSz>
constexpr Error model(T *io, ox::CommonPtrWith<SmallMap<K, V, SmallSz>> auto *obj) noexcept {
using Map = SmallMap<K, V, SmallSz>;
oxReturnError(io->template setTypeInfo<Map>());
oxReturnError(io->field("pairs", &obj->m_pairs));
return {};
}
}

View File

@ -26,7 +26,7 @@
namespace ox {
#if defined(OX_USE_STDLIB) && __has_include(<unistd.h>)
[[nodiscard]]
[[nodiscard]] [[maybe_unused]]
static auto symbolicate([[maybe_unused]]void **frames,
[[maybe_unused]]std::size_t frameCnt,
[[maybe_unused]]const char *linePrefix) {

View File

@ -16,6 +16,7 @@
#include "istring.hpp"
#include "byteswap.hpp"
#include "concepts.hpp"
#include "conv.hpp"
#include "cstringview.hpp"
#include "cstrops.hpp"
#include "def.hpp"

View File

@ -13,6 +13,7 @@ namespace ox {
template class BasicString<8>;
static_assert(StringView("Write") != String(""));
static_assert(ox::strcmp(String{}.c_str(), "\0") == 0);
static_assert(String("Write") != StringView(""));
static_assert(String("Write") == StringView("Write"));
static_assert(String(StringView("Write")) == StringView("Write"));

View File

@ -193,7 +193,9 @@ class BasicString {
constexpr BasicString substr(std::size_t begin, std::size_t end) const noexcept;
constexpr void resize(size_t sz) noexcept {
++sz;
m_buff.resize(sz);
m_buff[sz - 1] = 0;
}
[[nodiscard]]
@ -248,38 +250,19 @@ class BasicString {
template<std::size_t SmallStringSize_v>
constexpr BasicString<SmallStringSize_v>::BasicString() noexcept {
if (!m_buff.empty()) {
m_buff[0] = 0;
} else {
m_buff.push_back(0);
}
m_buff.resize(1);
}
template<std::size_t SmallStringSize_v>
constexpr BasicString<SmallStringSize_v>::BasicString(std::size_t cap) noexcept: m_buff(cap + 1) {
// GCC complains if you don't do this pretty unnecessary size check
if (!m_buff.empty()) {
m_buff[0] = 0;
}
}
constexpr BasicString<SmallStringSize_v>::BasicString(std::size_t cap) noexcept: m_buff(cap + 1) {}
template<std::size_t SmallStringSize_v>
constexpr BasicString<SmallStringSize_v>::BasicString(const char *str) noexcept {
if (!m_buff.empty()) {
m_buff[0] = 0;
} else {
m_buff.push_back(0);
}
set(str);
}
template<std::size_t SmallStringSize_v>
constexpr BasicString<SmallStringSize_v>::BasicString(const char8_t *str) noexcept {
if (!m_buff.empty()) {
m_buff[0] = 0;
} else {
m_buff.push_back(0);
}
set(str);
}
@ -287,21 +270,15 @@ template<std::size_t SmallStringSize_v>
constexpr BasicString<SmallStringSize_v>::BasicString(const char *str, std::size_t size) noexcept {
m_buff.resize(size + 1);
ox::listcpy(m_buff.data(), str, size);
m_buff[size] = 0;
}
template<std::size_t SmallStringSize_v>
constexpr BasicString<SmallStringSize_v>::BasicString(StringLiteral const&str) noexcept:
BasicString(StringView{str.data(), str.bytes()}) {
BasicString(StringView{str.data(), str.len()}) {
}
template<std::size_t SmallStringSize_v>
constexpr BasicString<SmallStringSize_v>::BasicString(CRStringView str) noexcept {
if (m_buff.empty()) {
m_buff.push_back(0);
} else {
m_buff[0] = 0;
}
set(str);
}
@ -312,6 +289,8 @@ constexpr BasicString<SmallStringSize_v>::BasicString(const BasicString &other)
template<std::size_t SmallStringSize_v>
constexpr BasicString<SmallStringSize_v>::BasicString(BasicString &&other) noexcept: m_buff(std::move(other.m_buff)) {
other.m_buff.resize(1);
other.m_buff[0] = 0;
}
template<std::size_t SmallStringSize_v>
@ -357,6 +336,8 @@ template<std::size_t SmallStringSize_v>
constexpr BasicString<SmallStringSize_v> &BasicString<SmallStringSize_v>::operator=(BasicString &&src) noexcept {
if (this != &src) {
m_buff = std::move(src.m_buff);
src.m_buff.resize(1);
src.m_buff[0] = 0;
}
return *this;
}

View File

@ -18,12 +18,12 @@
#include <ox/std/std.hpp>
[[nodiscard]]
static uint64_t nowMs() {
static uint64_t steadyNowMs() {
#if __has_include(<chrono>)
using namespace std::chrono;
return static_cast<uint64_t>(
duration_cast<milliseconds>(
system_clock::now().time_since_epoch()).count());
steady_clock::now().time_since_epoch()).count());
#else
return 0;
#endif
@ -41,13 +41,13 @@ uint64_t timeMapStrToUuid(int elemCnt, int lookups, uint64_t seed = 4321) noexce
auto const keys = map.keys();
ox::Random rand;
// start
auto const startTime = nowMs();
auto const startTime = steadyNowMs();
for (int i = 0; i < lookups; ++i) {
auto const&k = keys[rand.gen() % keys.size()];
map[k];
oxExpect(map[k], ox::UUID::fromString(k).unwrap());
}
return nowMs() - startTime;
return steadyNowMs() - startTime;
}
template<typename Map = ox::SmallMap<ox::UUID, ox::String>>
@ -62,16 +62,16 @@ uint64_t timeMapUuidToStr(int elemCnt, int lookups, uint64_t seed = 4321) noexce
auto const keys = map.keys();
ox::Random rand;
// start
auto const startTime = nowMs();
auto const startTime = steadyNowMs();
for (int i = 0; i < lookups; ++i) {
auto const&k = keys[rand.gen() % keys.size()];
oxExpect(map[k], k.toString());
}
return nowMs() - startTime;
return steadyNowMs() - startTime;
}
static ox::Error compareMaps(int lookupCnt = 1'000'000) {
auto const seed = nowMs();
auto const seed = steadyNowMs();
uint64_t hashTime{};
uint64_t smallTime{};
int elemCnt = 1;

View File

@ -185,6 +185,8 @@ struct enable_if<true, T> {
using type = T;
};
template<bool B, typename T>
using enable_if_t = typename enable_if<B, T>::type;
template<typename T>
struct is_pointer {

View File

@ -23,17 +23,16 @@
namespace ox {
template<typename T>
struct Vec {
class Vec2 {
public:
using value_type = T;
using value_type = float;
using size_type = std::size_t;
static constexpr auto TypeName = "net.drinkingtea.ox.Point";
static constexpr auto TypeVersion = 1;
T x = 0;
T y = 0;
float x = 0;
float y = 0;
template<typename RefType = value_type&, typename PtrType = value_type*, bool reverse = false>
struct iterator: public ox::Iterator<std::bidirectional_iterator_tag, value_type> {
@ -141,14 +140,14 @@ struct Vec {
};
constexpr Vec() noexcept = default;
constexpr Vec2() noexcept = default;
template<typename ...Args>
constexpr Vec(T pX, T pY) noexcept: x(pX), y(pY) {
constexpr Vec2(float pX, float pY) noexcept: x(pX), y(pY) {
}
#if __has_include(<imgui.h>)
explicit constexpr Vec(const ImVec2 &v) noexcept: Vec(v.x, v.y) {
explicit constexpr Vec2(const ImVec2 &v) noexcept: Vec2(v.x, v.y) {
}
explicit inline operator ImVec2() const noexcept {
@ -228,7 +227,7 @@ struct Vec {
}
}
constexpr auto operator==(const Vec &v) const noexcept {
constexpr auto operator==(const Vec2 &v) const noexcept {
for (auto i = 0u; i < v.size(); ++i) {
if ((*this)[i] != v[i]) {
return false;
@ -237,29 +236,71 @@ struct Vec {
return true;
}
constexpr auto operator!=(const Vec &v) const noexcept {
constexpr auto operator!=(const Vec2 &v) const noexcept {
return !operator==(v);
}
explicit constexpr operator class Point() const noexcept;
explicit constexpr operator class Size() const noexcept;
[[nodiscard]]
constexpr std::size_t size() const noexcept {
return 2;
}
constexpr Vec2 operator+(float i) const noexcept {
return {x + i, y + i};
}
constexpr Vec2 operator+=(float i) noexcept {
x += i;
y += i;
return *this;
}
constexpr Vec2 operator-(float i) const noexcept {
return {x - i, y - i};
}
constexpr Vec2 operator-=(float i) noexcept {
x -= i;
y -= i;
return *this;
}
constexpr Vec2 operator*(float i) const noexcept {
return {x * i, y * i};
}
constexpr Vec2 operator*=(float i) noexcept {
x *= i;
y *= i;
return *this;
}
constexpr Vec2 operator/(float i) const noexcept {
return {x / i, y / i};
}
constexpr Vec2 operator/=(float i) noexcept {
x /= i;
y /= i;
return *this;
}
protected:
[[nodiscard]]
constexpr T *start() noexcept {
return &x;
constexpr float *start() noexcept {
return&x;
}
[[nodiscard]]
constexpr const T *start() const noexcept {
constexpr const float *start() const noexcept {
return &x;
}
};
using Vec2 = Vec<float>;
template<typename T>
constexpr Error model(T *io, ox::CommonPtrWith<Vec2> auto *obj) noexcept {
oxReturnError(io->template setTypeInfo<Vec2>());

View File

@ -271,7 +271,7 @@ class Vector: detail::VectorAllocator<T, Allocator, SmallVectorSize> {
constexpr bool contains(MaybeView_t<T> const&) const noexcept;
constexpr iterator<T&, T*, false> insert(
std::size_t pos, std::size_t cnt, T val) noexcept(useNoexcept);
std::size_t pos, std::size_t cnt, T const&val) noexcept(useNoexcept);
constexpr iterator<T&, T*, false> insert(std::size_t pos, T val) noexcept(useNoexcept);
@ -531,29 +531,23 @@ constexpr bool Vector<T, SmallVectorSize, Allocator>::contains(MaybeView_t<T> co
template<typename T, std::size_t SmallVectorSize, typename Allocator>
constexpr typename Vector<T, SmallVectorSize, Allocator>::template iterator<T&, T*, false>
Vector<T, SmallVectorSize, Allocator>::insert(
std::size_t pos, std::size_t cnt, T val) noexcept(useNoexcept) {
std::size_t pos, std::size_t cnt, T const&val) noexcept(useNoexcept) {
if (m_size + cnt > m_cap) {
reserveInsert(m_cap ? m_size + cnt : initialCap, pos, cnt);
if (pos < m_size) {
m_items[pos] = std::move(val);
} else {
for (auto i = 0u; i < cnt; ++i) {
std::construct_at(&m_items[pos + i], m_items[pos]);
}
}
if (pos < m_size) {
for (auto i = m_size + cnt - 1; i > pos; --i) {
std::construct_at(&m_items[i], std::move(m_items[i - cnt]));
}
for (auto i = pos; i < pos + cnt; ++i) {
m_items[i] = val;
}
} else {
if (pos < m_size) {
for (auto i = m_size + cnt - 1; i > pos; --i) {
std::construct_at(&m_items[i], std::move(m_items[i - cnt]));
}
m_items[pos] = std::move(val);
} else {
for (auto i = 0u; i < cnt; ++i) {
std::construct_at(&m_items[pos + i], m_items[pos]);
}
for (auto i = 0u; i < cnt; ++i) {
std::construct_at(&m_items[pos + i], m_items[pos]);
}
}
++m_size;
m_size += cnt;
return begin() + pos;
}
@ -562,20 +556,14 @@ constexpr typename Vector<T, SmallVectorSize, Allocator>::template iterator<T&,
Vector<T, SmallVectorSize, Allocator>::insert(std::size_t pos, T val) noexcept(useNoexcept) {
if (m_size == m_cap) {
reserveInsert(m_cap ? m_cap * 2 : initialCap, pos);
if (pos < m_size) {
m_items[pos] = std::move(val);
} else {
std::construct_at(&m_items[pos], m_items[pos]);
}
if (pos < m_size) {
for (auto i = m_size; i > pos; --i) {
std::construct_at(&m_items[i], std::move(m_items[i - 1]));
}
m_items[pos] = std::move(val);
} else {
if (pos < m_size) {
for (auto i = m_size; i > pos; --i) {
std::construct_at(&m_items[i], std::move(m_items[i - 1]));
}
m_items[pos] = std::move(val);
} else {
std::construct_at(&m_items[pos], m_items[pos]);
}
std::construct_at(&m_items[pos], std::move(val));
}
++m_size;
return begin() + pos;

View File

@ -162,18 +162,10 @@ classes in question.
### Error Handling
Exceptions are clean and nice in userland code running in environments with
expansive system resources, but they are a bit of a pain in small bare metal
environments.
The GBA build has them disabled.
Exceptions cause also the compiler to generate a great deal of extra code that
inflates the size of the binary.
The binary size bloat is often cited as one of the main reasons why many
embedded developers prefer C to C++.
Instead of throwing exceptions, all engine code must return Ox error codes.
For the sake of consistency, try to stick to Ox error codes in non-engine code
as well.
The GBA build has exceptions disabled.
Instead of throwing exceptions, all engine code must return ```ox::Error```s.
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.
Nostalgia and Ox both use ```ox::Error``` to report errors. ```ox::Error``` is
a struct that has overloaded operators to behave like an integer error code,
plus some extra fields to enhance debuggability.
@ -219,6 +211,30 @@ int caller2() {
std::cout << val << '\n';
return 0;
}
ox::Error caller3(int &i) {
return foo(i).moveTo(i);
}
ox::Error caller4(int &i) {
return foo(i).copyTo(i);
}
int caller5(int i) {
return foo(i).unwrap(); // unwrap will kill the program if there is an error
}
int caller6(int i) {
return foo(i).unwrapThrow(); // unwrap will throw if there is an error
}
int caller7(int i) {
return foo(i).or_value(0); // will return 0 if foo returned an error
}
ox::Result<uint64_t> caller8(int i) {
return foo(i).to<uint64_t>(); // will convert the result of foo to uint64_t
}
```
Lastly, there are a few macros available to help in passing ```ox::Error```s
@ -275,6 +291,7 @@ ox::Error engineCode() noexcept {
return OxError(0);
}
```
Ox also has the ```oxRequire``` macro, which will initialize a value if there is no error, and return if there is.
It aims to somewhat emulate the ```?``` operator in Rust and Swift.
@ -309,6 +326,9 @@ ox::Result<int> f2() noexcept {
* ```oxRequireT``` - oxRequire Throw
* ```oxRequireMT``` - oxRequire Mutable Throw
The throw variants of ```oxRequire``` are generally legacy code.
```ox::Result::unwrapThrow``` is generally preferred now.
### Logging and Output
Ox provides for logging and debug prints via the ```oxTrace```, ```oxDebug```, and ```oxError``` macros.
@ -526,19 +546,13 @@ a wrapper around the bare formats.
```cpp
#include <ox/mc/read.hpp>
ox::Result<NostalgiaPalette> loadPalette1(const Buffer &buff) noexcept {
ox::Result<NostalgiaPalette> loadPalette1(ox::BufferView const&buff) noexcept {
return ox::readMC<NostalgiaPalette>(buff);
}
ox::Result<NostalgiaPalette> loadPalette2(const Buffer &buff) noexcept {
return ox::readMC<NostalgiaPalette>(buff.data(), buff.size());
}
ox::Result<NostalgiaPalette> loadPalette3(const Buffer &buff) noexcept {
ox::Result<NostalgiaPalette> loadPalette2(ox::BufferView const&buff) noexcept {
NostalgiaPalette pal;
std::size_t sz = 0;
oxReturnError(ox::readMC(buff.data(), buff.size(), &pal, &sz));
buffer.resize(sz);
oxReturnError(ox::readMC(buff, pal));
return pal;
}
```
@ -548,7 +562,7 @@ ox::Result<NostalgiaPalette> loadPalette3(const Buffer &buff) noexcept {
```cpp
#include <ox/mc/write.hpp>
ox::Result<ox::Buffer> writeSpritePalette1(NostalgiaPalette *pal) noexcept {
ox::Result<ox::Buffer> writeSpritePalette1(NostalgiaPalette const&pal) noexcept {
ox::Buffer buffer(ox::units::MB);
std::size_t sz = 0;
oxReturnError(ox::writeMC(buffer.data(), buffer.size(), pal, &sz));
@ -556,7 +570,7 @@ ox::Result<ox::Buffer> writeSpritePalette1(NostalgiaPalette *pal) noexcept {
return buffer;
}
ox::Result<ox::Buffer> writeSpritePalette2(NostalgiaPalette *pal) noexcept {
ox::Result<ox::Buffer> writeSpritePalette2(NostalgiaPalette const&pal) noexcept {
return ox::writeMC(pal);
}
```
@ -568,17 +582,13 @@ ox::Result<ox::Buffer> writeSpritePalette2(NostalgiaPalette *pal) noexcept {
```cpp
#include <ox/oc/read.hpp>
ox::Result<NostalgiaPalette> loadPalette1(const Buffer &buff) noexcept {
ox::Result<NostalgiaPalette> loadPalette1(ox::BufferView const&buff) noexcept {
return ox::readOC<NostalgiaPalette>(buff);
}
ox::Result<NostalgiaPalette> loadPalette2(const Buffer &buff) noexcept {
return ox::readOC<NostalgiaPalette>(buff.data(), buff.size());
}
ox::Result<NostalgiaPalette> loadPalette3(const Buffer &buff) noexcept {
ox::Result<NostalgiaPalette> loadPalette2(ox::BufferView const&buff) noexcept {
NostalgiaPalette pal;
oxReturnError(ox::readOC(buff.data(), buff.size(), &pal));
oxReturnError(ox::readOC(buff, &pal));
return pal;
}
```
@ -588,13 +598,13 @@ ox::Result<NostalgiaPalette> loadPalette3(const Buffer &buff) noexcept {
```cpp
#include <ox/oc/write.hpp>
ox::Result<ox::Buffer> writeSpritePalette1(NostalgiaPalette *pal) noexcept {
ox::Result<ox::Buffer> writeSpritePalette1(NostalgiaPalette const&pal) noexcept {
ox::Buffer buffer(ox::units::MB);
oxReturnError(ox::writeOC(buffer.data(), buffer.size(), pal));
return buffer;
}
ox::Result<ox::Buffer> writeSpritePalette2(NostalgiaPalette *pal) noexcept {
ox::Result<ox::Buffer> writeSpritePalette2(NostalgiaPalette const&pal) noexcept {
return ox::writeOC(pal);
}
```
@ -606,17 +616,13 @@ ox::Result<ox::Buffer> writeSpritePalette2(NostalgiaPalette *pal) noexcept {
```cpp
#include <ox/claw/read.hpp>
ox::Result<NostalgiaPalette> loadPalette1(const Buffer &buff) noexcept {
ox::Result<NostalgiaPalette> loadPalette1(ox::BufferView const&buff) noexcept {
return ox::readClaw<NostalgiaPalette>(buff);
}
ox::Result<NostalgiaPalette> loadPalette2(const Buffer &buff) noexcept {
return ox::readClaw<NostalgiaPalette>(buff.data(), buff.size());
}
ox::Result<NostalgiaPalette> loadPalette3(const Buffer &buff) noexcept {
ox::Result<NostalgiaPalette> loadPalette2(ox::BufferView const&buff) noexcept {
NostalgiaPalette pal;
oxReturnError(ox::readClaw(buff.data(), buff.size(), &pal));
oxReturnError(ox::readClaw(buff, pal));
return pal;
}
```
@ -626,8 +632,8 @@ ox::Result<NostalgiaPalette> loadPalette3(const Buffer &buff) noexcept {
```cpp
#include <ox/claw/write.hpp>
ox::Result<ox::Buffer> writeSpritePalette(NostalgiaPalette *pal) noexcept {
return ox::writeClaw(&pal);
ox::Result<ox::Buffer> writeSpritePalette(NostalgiaPalette const&pal) noexcept {
return ox::writeClaw(pal);
}
```

View File

@ -4,6 +4,7 @@
#pragma once
#include <ox/std/math.hpp>
#include <ox/std/types.hpp>
namespace nostalgia::core {
@ -135,7 +136,7 @@ constexpr Color16 color16(int r, int g, int b, int a = 0) noexcept {
return static_cast<Color16>(ox::min<uint8_t>(static_cast<uint8_t>(r), 31))
| static_cast<Color16>(ox::min<uint8_t>(static_cast<uint8_t>(g), 31) << 5)
| static_cast<Color16>(ox::min<uint8_t>(static_cast<uint8_t>(b), 31) << 10)
| static_cast<Color16>(a << 15);
| static_cast<Color16>(a << 15);
}
[[nodiscard]]
@ -151,4 +152,13 @@ static_assert(color16(16, 31, 0) == 1008);
static_assert(color16(16, 31, 8) == 9200);
static_assert(color16(16, 32, 8) == 9200);
[[nodiscard]]
constexpr Color16 applySelectionColor(Color16 const color) noexcept {
namespace core = nostalgia::core;
auto const r = core::red16(color) / 2;
auto const g = (core::green16(color) + 20) / 2;
auto const b = (core::blue16(color) + 31) / 2;
return core::color16(r, g, b);
}
}

View File

@ -103,15 +103,21 @@ oxModelEnd()
void addEntry(TileSheetSet &set, ox::FileAddress path, int32_t begin = 0, int32_t size = -1) noexcept;
[[nodiscard]]
int tileColumns(Context&) noexcept;
[[nodiscard]]
int tileRows(Context&) noexcept;
ox::Error loadBgPalette(
Context &ctx,
size_t palBank,
Palette const&palette,
CompactPalette const&palette,
size_t page = 0) noexcept;
ox::Error loadSpritePalette(
Context &ctx,
Palette const&palette,
CompactPalette const&palette,
size_t page = 0) noexcept;
ox::Error loadBgPalette(

View File

@ -36,7 +36,43 @@ struct PaletteV2 {
ox::Vector<ox::Vector<Color16>> pages;
};
using Palette = PaletteV2;
struct PaletteV3 {
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.Palette";
static constexpr auto TypeVersion = 3;
static constexpr auto Preloadable = true;
struct ColorInfo {
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.Palette.ColorInfo";
static constexpr auto TypeVersion = 3;
ox::String name;
constexpr ColorInfo() noexcept = default;
constexpr explicit ColorInfo(ox::StringView pName) noexcept:
name(pName) {}
constexpr explicit ColorInfo(ox::String &&pName) noexcept:
name(std::move(pName)) {}
};
ox::Vector<ColorInfo> colorInfo;
ox::Vector<ox::Vector<Color16>> pages;
};
using Palette = PaletteV3;
struct CompactPaletteV1 {
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.Palette";
static constexpr auto TypeVersion = 2;
static constexpr auto Preloadable = true;
ox::Vector<ox::Vector<Color16>> pages{};
};
using CompactPalette = CompactPaletteV1;
[[nodiscard]]
constexpr bool valid(Palette const&p) noexcept {
auto const colors = p.colorInfo.size();
return ox::all_of(p.pages.begin(), p.pages.end(), [colors](auto const&page) {
return page.size() == colors;
});
}
[[nodiscard]]
constexpr Color16 color(Palette const&pal, size_t page, size_t idx) noexcept {
@ -47,13 +83,53 @@ constexpr Color16 color(Palette const&pal, size_t page, size_t idx) noexcept {
}
[[nodiscard]]
constexpr Color16 color(Palette const&pal, size_t idx) noexcept {
auto constexpr page = 0;
return color(pal, page, idx);
constexpr Color16 color(CompactPalette const&pal, size_t page, size_t idx) noexcept {
if (page < pal.pages.size() && idx < pal.pages[page].size()) [[likely]] {
return pal.pages[page][idx];
}
return 0;
}
[[nodiscard]]
constexpr size_t colors(Palette const&pal, size_t page = 0) noexcept {
constexpr Color16 color(Palette const&pal, size_t idx) noexcept {
return color(pal, 0, idx);
}
[[nodiscard]]
constexpr Color16 color(CompactPalette const&pal, size_t idx) noexcept {
return color(pal, 0, idx);
}
[[nodiscard]]
constexpr auto &colors(Palette &pal, size_t page = 0) noexcept {
return pal.pages[page];
}
[[nodiscard]]
constexpr auto &colors(CompactPalette &pal, size_t page = 0) noexcept {
return pal.pages[page];
}
[[nodiscard]]
constexpr auto &colors(Palette const&pal, size_t page = 0) noexcept {
return pal.pages[page];
}
[[nodiscard]]
constexpr auto &colors(CompactPalette const&pal, size_t page = 0) noexcept {
return pal.pages[page];
}
[[nodiscard]]
constexpr size_t colorCnt(Palette const&pal, size_t page = 0) noexcept {
if (page < pal.pages.size()) [[likely]] {
return pal.pages[page].size();
}
return 0;
}
[[nodiscard]]
constexpr size_t colorCnt(CompactPalette const&pal, size_t page = 0) noexcept {
if (page < pal.pages.size()) [[likely]] {
return pal.pages[page].size();
}
@ -69,6 +145,15 @@ constexpr size_t largestPage(Palette const&pal) noexcept {
return out;
}
[[nodiscard]]
constexpr size_t largestPage(CompactPalette const&pal) noexcept {
size_t out{};
for (auto const&page : pal.pages) {
out = ox::max(out, page.size());
}
return out;
}
oxModelBegin(NostalgiaPalette)
oxModelField(colors)
oxModelEnd()
@ -81,4 +166,17 @@ oxModelBegin(PaletteV2)
oxModelField(pages)
oxModelEnd()
oxModelBegin(PaletteV3::ColorInfo)
oxModelField(name)
oxModelEnd()
oxModelBegin(PaletteV3)
oxModelField(colorInfo)
oxModelField(pages)
oxModelEnd()
oxModelBegin(CompactPaletteV1)
oxModelField(pages)
oxModelEnd()
}

View File

@ -222,6 +222,14 @@ ox::Error setPixelCount(TileSheet::SubSheet &ss, int8_t pBpp, std::size_t cnt) n
[[nodiscard]]
unsigned pixelCnt(TileSheet::SubSheet const&ss, int8_t pBpp) noexcept;
/**
*
* @param ss
* @param pBpp
* @param sz size of Subsheet in tiles (not pixels)
*/
ox::Error resizeSubsheet(TileSheet::SubSheet &ss, int8_t pBpp, ox::Size const&sz) noexcept;
/**
* validateSubSheetIdx takes a SubSheetIdx and moves the index to the
* preceding or parent sheet if the current corresponding sheet does

View File

@ -22,8 +22,6 @@
namespace nostalgia::core {
constexpr auto GbaTileColumns = 32;
constexpr auto GbaTileRows = 32;
constexpr auto SpriteCount = 128;
struct GbaTileMapTarget {
@ -138,13 +136,13 @@ ox::Error initGfx(Context&, InitParams const&) noexcept {
ox::Error loadBgPalette(
Context&,
size_t palBank,
Palette const&palette,
CompactPalette const&palette,
size_t page) noexcept {
if (palette.pages.empty()) {
return {};
}
auto const paletteMem = MEM_BG_PALETTE + palBank * 16;
for (auto i = 0u; i < colors(palette, page); ++i) {
for (auto i = 0u; i < colorCnt(palette, page); ++i) {
paletteMem[i] = color(palette, page, i);
}
return {};
@ -152,13 +150,13 @@ ox::Error loadBgPalette(
ox::Error loadSpritePalette(
Context&,
Palette const&palette,
CompactPalette const&palette,
size_t page) noexcept {
if (palette.pages.empty()) {
return {};
}
auto const paletteMem = MEM_SPRITE_PALETTE;
for (auto i = 0u; i < colors(palette, page); ++i) {
for (auto i = 0u; i < colorCnt(palette, page); ++i) {
paletteMem[i] = color(palette, page, i);
}
return {};
@ -168,14 +166,14 @@ ox::Error loadBgPalette(
Context &ctx,
size_t palBank,
ox::FileAddress const&paletteAddr) noexcept {
oxRequire(pal, keel::readObj<Palette>(keelCtx(ctx), paletteAddr));
oxRequire(pal, keel::readObj<CompactPalette>(keelCtx(ctx), paletteAddr));
return loadBgPalette(ctx, palBank, *pal, 0);
}
ox::Error loadSpritePalette(
Context &ctx,
ox::FileAddress const&paletteAddr) noexcept {
oxRequire(pal, keel::readObj<Palette>(keelCtx(ctx), paletteAddr));
oxRequire(pal, keel::readObj<CompactPalette>(keelCtx(ctx), paletteAddr));
return loadSpritePalette(ctx, *pal, 0);
}
@ -284,8 +282,8 @@ ox::Error loadSpriteTileSheet(
return {};
}
void setBgTile(Context&, uint_t bgIdx, int column, int row, BgTile const&tile) noexcept {
auto const tileIdx = static_cast<std::size_t>(row * GbaTileColumns + column);
void setBgTile(Context &ctx, uint_t bgIdx, int column, int row, BgTile const&tile) noexcept {
auto const tileIdx = static_cast<std::size_t>(row * tileColumns(ctx) + column);
// see Tonc 9.3
MEM_BG_MAP[bgIdx][tileIdx] =
static_cast<uint16_t>(tile.tileIdx & 0b1'1111'1111) |
@ -294,8 +292,8 @@ void setBgTile(Context&, uint_t bgIdx, int column, int row, BgTile const&tile) n
static_cast<uint16_t>(tile.palBank << 0xc);
}
void clearBg(Context&, uint_t bgIdx) noexcept {
memset(MEM_BG_MAP[bgIdx].data(), 0, GbaTileRows * GbaTileColumns);
void clearBg(Context &ctx, uint_t bgIdx) noexcept {
memset(MEM_BG_MAP[bgIdx].data(), 0, static_cast<size_t>(tileRows(ctx) * tileColumns(ctx)));
}
uint8_t bgStatus(Context&) noexcept {

View File

@ -6,6 +6,17 @@
namespace nostalgia::core {
constexpr auto GbaTileColumns = 32;
constexpr auto GbaTileRows = 32;
int tileColumns(Context&) noexcept {
return GbaTileColumns;
}
int tileRows(Context&) noexcept {
return GbaTileRows;
}
// map ASCII values to the nostalgia charset
constexpr ox::Array<char, 128> charMap = {
0,

View File

@ -18,6 +18,8 @@ static class: public keel::Module {
private:
NostalgiaPaletteToPaletteV1Converter m_nostalgiaPaletteToPaletteV1Converter;
PaletteV1ToPaletteV2Converter m_paletteV1ToPaletteV2Converter;
PaletteV2ToPaletteV3Converter m_paletteV2ToPaletteV3Converter;
PaletteToCompactPaletteConverter m_paletteToCompactPaletteConverter;
TileSheetV1ToTileSheetV2Converter m_tileSheetV1ToTileSheetV2Converter;
TileSheetV2ToTileSheetV3Converter m_tileSheetV2ToTileSheetV3Converter;
TileSheetV3ToTileSheetV4Converter m_tileSheetV3ToTileSheetV4Converter;
@ -47,6 +49,8 @@ static class: public keel::Module {
return {
&m_nostalgiaPaletteToPaletteV1Converter,
&m_paletteV1ToPaletteV2Converter,
&m_paletteV2ToPaletteV3Converter,
&m_paletteToCompactPaletteConverter,
&m_tileSheetV1ToTileSheetV2Converter,
&m_tileSheetV2ToTileSheetV3Converter,
&m_tileSheetV3ToTileSheetV4Converter,
@ -71,8 +75,10 @@ static class: public keel::Module {
},
[](keel::Context &ctx, ox::Buffer &buff, ox::StringView typeId) -> ox::Result<bool> {
if (typeId == ox::ModelTypeId_v<NostalgiaPalette> ||
typeId == ox::ModelTypeId_v<PaletteV1>) {
oxReturnError(keel::convertBuffToBuff<Palette>(
typeId == ox::ModelTypeId_v<PaletteV1> ||
typeId == ox::ModelTypeId_v<PaletteV2> ||
typeId == ox::ModelTypeId_v<PaletteV3>) {
oxReturnError(keel::convertBuffToBuff<CompactPalette>(
ctx, buff, ox::ClawFormat::Metal).moveTo(buff));
return true;
}

View File

@ -22,6 +22,27 @@ ox::Error PaletteV1ToPaletteV2Converter::convert(
return {};
}
ox::Error PaletteV2ToPaletteV3Converter::convert(
keel::Context&,
PaletteV2 &src,
PaletteV3 &dst) const noexcept {
dst.pages = std::move(src.pages);
if (!dst.pages.empty()) {
for (size_t i = 0; i < dst.pages[0].size(); ++i) {
dst.colorInfo.emplace_back(ox::sfmt("Color {}", i));
}
}
return {};
}
ox::Error PaletteToCompactPaletteConverter::convert(
keel::Context&,
Palette &src,
CompactPalette &dst) const noexcept {
dst.pages = std::move(src.pages);
return {};
}
ox::Error TileSheetV1ToTileSheetV2Converter::convert(
keel::Context&,
TileSheetV1 &src,

View File

@ -21,7 +21,15 @@ class NostalgiaPaletteToPaletteV1Converter: public keel::Converter<NostalgiaPale
};
class PaletteV1ToPaletteV2Converter: public keel::Converter<PaletteV1, PaletteV2> {
ox::Error convert(keel::Context&, PaletteV1 &src, PaletteV2 &dst) const noexcept final;
ox::Error convert(keel::Context&, PaletteV1 &src, PaletteV2 &dst) const noexcept final;
};
class PaletteV2ToPaletteV3Converter: public keel::Converter<PaletteV2, PaletteV3> {
ox::Error convert(keel::Context&, PaletteV2 &src, PaletteV3 &dst) const noexcept final;
};
class PaletteToCompactPaletteConverter: public keel::Converter<Palette, CompactPalette> {
ox::Error convert(keel::Context&, Palette &src, CompactPalette &dst) const noexcept final;
};
class TileSheetV1ToTileSheetV2Converter: public keel::Converter<TileSheetV1, TileSheetV2> {

View File

@ -364,7 +364,7 @@ static void loadPalette(
ox::Array<GLfloat, 1024> &palette,
size_t palOffset,
GLuint shaderPgrm,
Palette const&pal,
CompactPalette const&pal,
size_t page = 0) noexcept {
static constexpr std::size_t ColorCnt = 256;
for (auto i = palOffset; auto const c : pal.pages[page]) {
@ -385,7 +385,8 @@ static void setSprite(
uint_t const idx,
Sprite const&s) noexcept {
// Tonc Table 8.4
static constexpr ox::Array<ox::Vec<uint_t>, 12> dimensions{
struct Sz { uint_t x{}, y{}; };
static constexpr ox::Array<Sz, 12> dimensions{
// col 0
{1, 1}, // 0, 0
{2, 2}, // 0, 1
@ -515,7 +516,7 @@ static ox::Result<TileSheetData> normalizeTileSheet(
ox::Error loadBgPalette(
Context &ctx,
size_t palBank,
Palette const&palette,
CompactPalette const&palette,
size_t page) noexcept {
renderer::loadPalette(ctx.bgPalette, palBank * 16 * 4, ctx.bgShader, palette, page);
return {};
@ -523,7 +524,7 @@ ox::Error loadBgPalette(
ox::Error loadSpritePalette(
Context &ctx,
Palette const&palette,
CompactPalette const&palette,
size_t page) noexcept {
ox::Array<GLfloat, 1024> pal;
renderer::loadPalette(pal, 0, ctx.spriteShader, palette, page);
@ -535,7 +536,7 @@ ox::Error loadBgPalette(
size_t palBank,
ox::FileAddress const&paletteAddr) noexcept {
auto &kctx = keelCtx(ctx.turbineCtx);
oxRequire(palette, readObj<Palette>(kctx, paletteAddr));
oxRequire(palette, readObj<CompactPalette>(kctx, paletteAddr));
renderer::loadPalette(ctx.bgPalette, palBank * 16 * 4, ctx.bgShader, *palette);
return {};
}
@ -544,7 +545,7 @@ ox::Error loadSpritePalette(
Context &ctx,
ox::FileAddress const&paletteAddr) noexcept {
auto &kctx = keelCtx(ctx.turbineCtx);
oxRequire(palette, readObj<Palette>(kctx, paletteAddr));
oxRequire(palette, readObj<CompactPalette>(kctx, paletteAddr));
ox::Array<GLfloat, 1024> pal;
renderer::loadPalette(pal, 0, ctx.spriteShader, *palette);
return {};

View File

@ -15,11 +15,16 @@
namespace nostalgia::core {
PaletteEditorImGui::PaletteEditorImGui(studio::StudioContext &sctx, ox::CRStringView path):
Editor(path),
m_sctx(sctx),
m_tctx(sctx.tctx),
m_pal(*keel::readObj<Palette>(keelCtx(m_tctx), ox::FileAddress(itemPath())).unwrapThrow()) {
namespace ig = studio::ig;
PaletteEditorImGui::PaletteEditorImGui(studio::StudioContext &sctx, ox::StringView const path):
Editor(path),
m_sctx(sctx),
m_tctx(sctx.tctx),
m_pal(*keel::readObj<Palette>(keelCtx(m_tctx), itemPath()).unwrapThrow()) {
if (!valid(m_pal)) {
throw OxException(1, "PaletteEditorImGui: invalid Palette object");
}
undoStack()->changeTriggered.connect(this, &PaletteEditorImGui::handleCommand);
}
@ -36,10 +41,9 @@ void PaletteEditorImGui::keyStateChanged(turbine::Key key, bool down) {
if (turbine::buttonDown(m_tctx, turbine::Key::Mod_Alt)) {
m_selectedColorRow =
ox::min<std::size_t>(
static_cast<uint32_t>(key - turbine::Key::Num_1 + 9), m_pal.pages.size() - 1);
static_cast<uint32_t>(key - turbine::Key::Num_1 + 9), m_pal.pages.size() - 1);
}
}
}
void PaletteEditorImGui::draw(studio::StudioContext&) noexcept {
@ -58,7 +62,12 @@ void PaletteEditorImGui::draw(studio::StudioContext&) noexcept {
}
ox::Error PaletteEditorImGui::saveItem() noexcept {
return m_sctx.project->writeObj(itemPath(), m_pal);
return m_sctx.project->writeObj(itemPath(), m_pal, ox::ClawFormat::Organic);
}
void PaletteEditorImGui::drawColumnLeftAlign(ox::CStringView txt) noexcept {
ImGui::TableNextColumn();
ImGui::Text("%s", txt.c_str());
}
void PaletteEditorImGui::drawColumn(ox::CStringView txt) noexcept {
@ -71,42 +80,39 @@ void PaletteEditorImGui::drawColumn(ox::CStringView txt) noexcept {
void PaletteEditorImGui::drawColorsEditor() noexcept {
constexpr auto tableFlags = ImGuiTableFlags_RowBg;
auto const colorsSz = ImGui::GetContentRegionAvail();
auto const colorEditor = m_selectedColorRow < colors(m_pal, m_page);
auto const colorEditor = m_selectedColorRow < colorCnt(m_pal, m_page);
auto const colorEditorWidth = 220;
static constexpr auto toolbarHeight = 40;
{
auto const sz = ImVec2(70, 24);
if (ImGui::Button("Add", sz)) {
auto const colorSz = static_cast<int>(colors(m_pal, m_page));
auto const colorSz = colorCnt(m_pal, m_page);
constexpr Color16 c = 0;
undoStack()->push(ox::make_unique<AddColorCommand>(&m_pal, c, m_page, colorSz));
std::ignore = pushCommand<AddColorCommand>(m_pal, c, colorSz);
}
ImGui::SameLine();
ImGui::BeginDisabled(m_selectedColorRow >= colors(m_pal, m_page));
ImGui::BeginDisabled(m_selectedColorRow >= colorCnt(m_pal, m_page));
{
if (ImGui::Button("Remove", sz)) {
undoStack()->push(
ox::make_unique<RemoveColorCommand>(
&m_pal,
color(m_pal, m_page, static_cast<std::size_t>(m_selectedColorRow)),
m_page,
static_cast<int>(m_selectedColorRow)));
m_selectedColorRow = ox::min(colors(m_pal, m_page) - 1, m_selectedColorRow);
std::ignore = pushCommand<RemoveColorCommand>(m_pal, m_selectedColorRow);
m_selectedColorRow = ox::min(colorCnt(m_pal, m_page) - 1, m_selectedColorRow);
}
ImGui::SameLine();
ImGui::BeginDisabled(m_selectedColorRow <= 0);
{
if (ImGui::Button("Move Up", sz)) {
undoStack()->push(ox::make_unique<MoveColorCommand>(&m_pal, m_page, m_selectedColorRow, -1));
std::ignore = pushCommand<MoveColorCommand>(
m_pal, m_page, m_selectedColorRow, m_selectedColorRow - 1);
--m_selectedColorRow;
}
}
ImGui::EndDisabled();
ImGui::SameLine();
ImGui::BeginDisabled(m_selectedColorRow >= colors(m_pal, m_page) - 1);
ImGui::BeginDisabled(m_selectedColorRow >= colorCnt(m_pal, m_page) - 1);
{
if (ImGui::Button("Move Down", sz)) {
undoStack()->push(ox::make_unique<MoveColorCommand>(&m_pal, m_page, m_selectedColorRow, 1));
std::ignore = pushCommand<MoveColorCommand>(
m_pal, m_page, m_selectedColorRow, m_selectedColorRow + 1);
++m_selectedColorRow;
}
}
@ -114,26 +120,35 @@ void PaletteEditorImGui::drawColorsEditor() noexcept {
}
ImGui::EndDisabled();
}
auto const tblWidth = (colorsSz.x - colorEditorWidth - 8) * colorEditor;
ImGui::BeginTable("Colors", 5, tableFlags, ImVec2(tblWidth, colorsSz.y - (toolbarHeight + 5)));
auto const tblWidth = (colorsSz.x - static_cast<float>(colorEditorWidth) - 8.f)
* static_cast<float>(colorEditor);
ImGui::BeginTable(
"Colors",
6,
tableFlags,
ImVec2(tblWidth, colorsSz.y - (toolbarHeight + 5)));
{
ImGui::TableSetupColumn("Idx", ImGuiTableColumnFlags_WidthFixed, 25);
ImGui::TableSetupColumn("Red", ImGuiTableColumnFlags_WidthFixed, 50);
ImGui::TableSetupColumn("Green", ImGuiTableColumnFlags_WidthFixed, 50);
ImGui::TableSetupColumn("Blue", ImGuiTableColumnFlags_WidthFixed, 50);
ImGui::TableSetupColumn("Color Preview", ImGuiTableColumnFlags_NoHide);
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 100);
ImGui::TableSetupColumn("Red", ImGuiTableColumnFlags_WidthFixed, 40);
ImGui::TableSetupColumn("Green", ImGuiTableColumnFlags_WidthFixed, 40);
ImGui::TableSetupColumn("Blue", ImGuiTableColumnFlags_WidthFixed, 40);
ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_NoHide);
ImGui::TableHeadersRow();
for (auto i = 0u; auto const c : m_pal.pages[m_page]) {
for (auto i = 0u; auto const&c : m_pal.pages[m_page]) {
ImGui::PushID(static_cast<int>(i));
ImGui::TableNextRow();
drawColumn(i);
drawColumn(i + 1);
drawColumnLeftAlign(m_pal.colorInfo[i].name);
drawColumn(red16(c));
drawColumn(green16(c));
drawColumn(blue16(c));
ImGui::TableNextColumn();
auto const ic = ImGui::GetColorU32(ImVec4(redf(c), greenf(c), bluef(c), 1));
auto const ic = ImGui::GetColorU32(
ImVec4(redf(c), greenf(c), bluef(c), 1));
ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, ic);
if (ImGui::Selectable("##ColorRow", i == m_selectedColorRow, ImGuiSelectableFlags_SpanAllColumns)) {
if (ImGui::Selectable(
"##ColorRow", i == m_selectedColorRow, ImGuiSelectableFlags_SpanAllColumns)) {
m_selectedColorRow = i;
}
ImGui::PopID();
@ -155,28 +170,30 @@ void PaletteEditorImGui::drawPagesEditor() noexcept {
constexpr auto toolbarHeight = 40;
auto const btnSz = ImVec2(paneSz.x / 3 - 5.5f, 24);
if (ImGui::Button("Add", btnSz)) {
undoStack()->push(ox::make_unique<AddPageCommand>(m_pal));
std::ignore = pushCommand<DuplicatePageCommand>(m_pal, 0u, m_pal.pages.size());
m_page = m_pal.pages.size() - 1;
}
ImGui::SameLine();
if (ImGui::Button("Remove", btnSz)) {
undoStack()->push(ox::make_unique<RemovePageCommand>(m_pal, m_page));
std::ignore = pushCommand<RemovePageCommand>(m_pal, m_page);
m_page = std::min(m_page, m_pal.pages.size() - 1);
}
ImGui::SameLine();
if (ImGui::Button("Duplicate", btnSz)) {
undoStack()->push(ox::make_unique<DuplicatePageCommand>(m_pal, m_page, m_pal.pages.size()));
std::ignore = pushCommand<DuplicatePageCommand>(m_pal, m_page, m_pal.pages.size());
}
ImGui::BeginTable("PageSelect", 2, tableFlags, ImVec2(paneSz.x, paneSz.y - (toolbarHeight + 5)));
ImGui::BeginTable(
"PageSelect",
2,
tableFlags,
ImVec2(paneSz.x, paneSz.y - (toolbarHeight + 5)));
{
ImGui::TableSetupColumn("Page", ImGuiTableColumnFlags_WidthFixed, 60);
ImGui::TableSetupColumn("Colors", ImGuiTableColumnFlags_NoHide);
ImGui::TableHeadersRow();
for (auto i = 0u; i < m_pal.pages.size(); ++i) {
ImGui::PushID(static_cast<int>(i));
ImGui::TableNextRow();
drawColumn(i + 1);
drawColumn(colors(m_pal, i));
ImGui::SameLine();
if (ImGui::Selectable("##PageRow", i == m_page, ImGuiSelectableFlags_SpanAllColumns)) {
m_page = i;
@ -193,20 +210,33 @@ void PaletteEditorImGui::drawColorEditor() noexcept {
int g = green16(c);
int b = blue16(c);
int const a = alpha16(c);
auto const&currentName = m_pal.colorInfo[m_selectedColorRow].name;
ox::IString<50> name;
name = currentName;
ImGui::InputText("Name", name.data(), name.cap() + 1);
ImGui::Separator();
ImGui::InputInt("Red", &r, 1, 5);
ImGui::InputInt("Green", &g, 1, 5);
ImGui::InputInt("Blue", &b, 1, 5);
if (ig::PushButton("Apply to all pages", {-1, ig::BtnSz.y})) {
std::ignore = pushCommand<ApplyColorAllPagesCommand>(
m_pal, m_page, m_selectedColorRow);
}
r = ox::max(r, 0);
g = ox::max(g, 0);
b = ox::max(b, 0);
auto const newColor = color16(r, g, b, a);
if (c != newColor) {
undoStack()->push(ox::make_unique<UpdateColorCommand>(
&m_pal, m_page, static_cast<int>(m_selectedColorRow), c, newColor));
std::ignore = pushCommand<UpdateColorCommand>(m_pal, m_page, m_selectedColorRow, newColor);
}
if (currentName != name.data()) {
std::ignore = pushCommand<UpdateColorInfoCommand>(
m_pal, m_selectedColorRow, Palette::ColorInfo{name.data()});
}
}
ox::Error PaletteEditorImGui::handleCommand(studio::UndoCommand const*cmd) noexcept {
if (dynamic_cast<AddPageCommand const*>(cmd)) {
m_page = m_pal.pages.size() - 1;
} else if (dynamic_cast<RemovePageCommand const*>(cmd)) {
if (dynamic_cast<RemovePageCommand const*>(cmd)) {
m_page = ox::min(m_page, m_pal.pages.size() - 1);
} else if (auto const dupPageCmd = dynamic_cast<DuplicatePageCommand const*>(cmd)) {
m_page = ox::clamp<size_t>(dupPageCmd->insertIdx(), 0, m_pal.pages.size() - 1);

View File

@ -21,7 +21,7 @@ class PaletteEditorImGui: public studio::Editor {
size_t m_page = 0;
public:
PaletteEditorImGui(studio::StudioContext &sctx, ox::CRStringView path);
PaletteEditorImGui(studio::StudioContext &sctx, ox::StringView path);
void keyStateChanged(turbine::Key key, bool down) override;
@ -31,6 +31,8 @@ class PaletteEditorImGui: public studio::Editor {
ox::Error saveItem() noexcept final;
private:
static void drawColumnLeftAlign(ox::CStringView txt) noexcept;
static void drawColumn(ox::CStringView txt) noexcept;
static void drawColumn(ox::Integer_c auto i) noexcept {

View File

@ -6,20 +6,44 @@
namespace nostalgia::core {
AddPageCommand::AddPageCommand(Palette &pal) noexcept:
m_pal(pal) {
ApplyColorAllPagesCommand::ApplyColorAllPagesCommand(Palette &pal, size_t const page, size_t const idx):
m_pal(pal),
m_page(page),
m_idx(idx),
m_origColors([this] {
ox::Vector<Color16> colors;
colors.reserve(m_pal.pages.size());
for (auto const&p : m_pal.pages) {
colors.emplace_back(p[m_idx]);
}
return colors;
}()) {
auto const c = color(m_pal, m_page, m_idx);
if (ox::all_of(m_pal.pages.begin(), m_pal.pages.end(), [this, c](ox::SpanView<Color16> const page) {
return page[m_idx] == c;
})) {
throw studio::NoChangesException();
}
}
int AddPageCommand::commandId() const noexcept {
return static_cast<int>(PaletteEditorCommandId::AddPage);
int ApplyColorAllPagesCommand::commandId() const noexcept {
return static_cast<int>(PaletteEditorCommandId::ApplyColorAllPages);
}
void AddPageCommand::redo() noexcept {
m_pal.pages.emplace_back();
ox::Error ApplyColorAllPagesCommand::redo() noexcept {
auto const c = color(m_pal, m_page, m_idx);
for (auto &page : m_pal.pages) {
page[m_idx] = c;
}
return {};
}
void AddPageCommand::undo() noexcept {
std::ignore = m_pal.pages.erase(static_cast<std::size_t>(m_pal.pages.size() - 1));
ox::Error ApplyColorAllPagesCommand::undo() noexcept {
for (size_t p = 0u; auto &page : m_pal.pages) {
page[m_idx] = m_origColors[p];
++p;
}
return {};
}
@ -37,13 +61,14 @@ int DuplicatePageCommand::commandId() const noexcept {
return static_cast<int>(PaletteEditorCommandId::DuplicatePage);
}
void DuplicatePageCommand::redo() noexcept {
ox::Error DuplicatePageCommand::redo() noexcept {
m_pal.pages.emplace(m_dstIdx, std::move(m_page));
return {};
}
void DuplicatePageCommand::undo() noexcept {
ox::Error DuplicatePageCommand::undo() noexcept {
m_page = std::move(m_pal.pages[m_dstIdx]);
std::ignore = m_pal.pages.erase(static_cast<std::size_t>(m_dstIdx));
return m_pal.pages.erase(static_cast<std::size_t>(m_dstIdx)).error;
}
size_t DuplicatePageCommand::insertIdx() const noexcept {
@ -53,86 +78,144 @@ size_t DuplicatePageCommand::insertIdx() const noexcept {
RemovePageCommand::RemovePageCommand(Palette &pal, size_t idx) noexcept:
m_pal(pal),
m_idx(idx) {
}
m_idx(idx) {}
int RemovePageCommand::commandId() const noexcept {
return static_cast<int>(PaletteEditorCommandId::RemovePage);
}
void RemovePageCommand::redo() noexcept {
m_page = std::move(m_pal.pages[m_idx]);
std::ignore = m_pal.pages.erase(static_cast<std::size_t>(m_idx));
ox::Error RemovePageCommand::redo() noexcept {
m_page = std::move(colors(m_pal, m_idx));
return m_pal.pages.erase(static_cast<std::size_t>(m_idx)).error;
}
void RemovePageCommand::undo() noexcept {
m_pal.pages.insert(m_idx, std::move(m_page));
ox::Error RemovePageCommand::undo() noexcept {
m_pal.pages.emplace(m_idx, std::move(m_page));
return {};
}
AddColorCommand::AddColorCommand(Palette *pal, Color16 color, size_t page, int idx) noexcept {
m_pal = pal;
m_color = color;
m_page = page;
m_idx = idx;
}
AddColorCommand::AddColorCommand(Palette &pal, Color16 const color, size_t const idx) noexcept:
m_pal(pal),
m_color(color),
m_idx(idx) {}
int AddColorCommand::commandId() const noexcept {
return static_cast<int>(PaletteEditorCommandId::AddColor);
}
void AddColorCommand::redo() noexcept {
m_pal->pages[m_page].insert(static_cast<std::size_t>(m_idx), m_color);
ox::Error AddColorCommand::redo() noexcept {
for (auto &page : m_pal.pages) {
page.emplace(static_cast<size_t>(m_idx), m_color);
}
return {};
}
void AddColorCommand::undo() noexcept {
std::ignore = m_pal->pages[m_page].erase(static_cast<std::size_t>(m_idx));
ox::Error AddColorCommand::undo() noexcept {
for (auto &page : m_pal.pages) {
oxReturnError(page.erase(static_cast<std::size_t>(m_idx)));
}
return {};
}
RemoveColorCommand::RemoveColorCommand(Palette *pal, Color16 color, size_t page, int idx) noexcept {
m_pal = pal;
m_color = color;
m_page = page;
m_idx = idx;
}
RemoveColorCommand::RemoveColorCommand(Palette &pal, size_t const idx) noexcept:
m_pal(pal),
m_idx(idx),
m_colors([this] {
ox::Vector<Color16> colors;
colors.reserve(m_pal.pages.size());
for (auto const&p : m_pal.pages) {
colors.emplace_back(p[m_idx]);
}
return colors;
}()) {}
int RemoveColorCommand::commandId() const noexcept {
return static_cast<int>(PaletteEditorCommandId::RemoveColor);
}
void RemoveColorCommand::redo() noexcept {
std::ignore = m_pal->pages[m_page].erase(static_cast<std::size_t>(m_idx));
ox::Error RemoveColorCommand::redo() noexcept {
for (auto &page : m_pal.pages) {
oxReturnError(page.erase(m_idx));
}
return {};
}
void RemoveColorCommand::undo() noexcept {
m_pal->pages[m_page].insert(static_cast<std::size_t>(m_idx), m_color);
ox::Error RemoveColorCommand::undo() noexcept {
for (size_t p = 0; auto &page : m_pal.pages) {
page.emplace(m_idx, m_colors[p]);
++p;
}
return {};
}
UpdateColorInfoCommand::UpdateColorInfoCommand(
Palette &pal,
size_t idx,
Palette::ColorInfo newColorInfo):
m_pal(pal),
m_idx(idx),
m_altColorInfo(std::move(newColorInfo)) {
if (m_pal.colorInfo[m_idx].name == m_altColorInfo.name) {
throw studio::NoChangesException();
}
}
bool UpdateColorInfoCommand::mergeWith(UndoCommand const&cmd) noexcept {
if (cmd.commandId() != static_cast<int>(PaletteEditorCommandId::UpdateColor)) {
return false;
}
auto ucCmd = static_cast<UpdateColorInfoCommand const*>(&cmd);
if (m_idx != ucCmd->m_idx) {
return false;
}
return true;
}
[[nodiscard]]
int UpdateColorInfoCommand::commandId() const noexcept {
return static_cast<int>(PaletteEditorCommandId::UpdateColor);
}
ox::Error UpdateColorInfoCommand::redo() noexcept {
swap();
return {};
}
ox::Error UpdateColorInfoCommand::undo() noexcept {
swap();
return {};
}
void UpdateColorInfoCommand::swap() noexcept {
std::swap(m_pal.colorInfo[m_idx], m_altColorInfo);
}
UpdateColorCommand::UpdateColorCommand(
Palette *pal,
size_t page,
int idx,
Color16 oldColor,
Color16 newColor) noexcept {
m_pal = pal;
m_page = page;
m_idx = idx;
m_oldColor = oldColor;
m_newColor = newColor;
//setObsolete(m_oldColor == m_newColor);
Palette &pal,
size_t page,
size_t idx,
Color16 newColor):
m_pal(pal),
m_page(page),
m_idx(idx),
m_altColor(newColor) {
if (color(m_pal, m_page, m_idx) == newColor) {
throw studio::NoChangesException();
}
}
bool UpdateColorCommand::mergeWith(const UndoCommand *cmd) noexcept {
if (cmd->commandId() != static_cast<int>(PaletteEditorCommandId::UpdateColor)) {
bool UpdateColorCommand::mergeWith(UndoCommand const&cmd) noexcept {
if (cmd.commandId() != static_cast<int>(PaletteEditorCommandId::UpdateColor)) {
return false;
}
auto ucCmd = static_cast<const UpdateColorCommand*>(cmd);
auto ucCmd = static_cast<UpdateColorCommand const*>(&cmd);
if (m_idx != ucCmd->m_idx) {
return false;
}
m_newColor = ucCmd->m_newColor;
return true;
}
@ -141,38 +224,47 @@ int UpdateColorCommand::commandId() const noexcept {
return static_cast<int>(PaletteEditorCommandId::UpdateColor);
}
void UpdateColorCommand::redo() noexcept {
m_pal->pages[m_page][static_cast<std::size_t>(m_idx)] = m_newColor;
ox::Error UpdateColorCommand::redo() noexcept {
swap();
return {};
}
void UpdateColorCommand::undo() noexcept {
m_pal->pages[m_page][static_cast<std::size_t>(m_idx)] = m_oldColor;
ox::Error UpdateColorCommand::undo() noexcept {
swap();
return {};
}
void UpdateColorCommand::swap() noexcept {
auto &dst = colors(m_pal, m_page)[m_idx];
std::swap(dst, m_altColor);
}
MoveColorCommand::MoveColorCommand(Palette *pal, size_t page, std::size_t idx, int offset) noexcept {
m_pal = pal;
m_page = page;
m_idx = idx;
m_offset = offset;
}
MoveColorCommand::MoveColorCommand(
Palette &pal, size_t page, size_t srcIdx, size_t dstIdx) noexcept:
m_pal(pal),
m_page(page),
m_srcIdx(srcIdx),
m_dstIdx(dstIdx) {}
int MoveColorCommand::commandId() const noexcept {
return static_cast<int>(PaletteEditorCommandId::MoveColor);
}
void MoveColorCommand::redo() noexcept {
moveColor(static_cast<int>(m_idx), m_offset);
ox::Error MoveColorCommand::redo() noexcept {
moveColor(m_srcIdx, m_dstIdx);
return {};
}
void MoveColorCommand::undo() noexcept {
moveColor(static_cast<int>(m_idx) + m_offset, -m_offset);
ox::Error MoveColorCommand::undo() noexcept {
moveColor(m_dstIdx, m_srcIdx);
return {};
}
void MoveColorCommand::moveColor(int idx, int offset) noexcept {
const auto c = m_pal->pages[m_page][static_cast<std::size_t>(idx)];
std::ignore = m_pal->pages[m_page].erase(static_cast<std::size_t>(idx));
m_pal->pages[m_page].insert(static_cast<std::size_t>(idx + offset), c);
void MoveColorCommand::moveColor(size_t srcIdx, size_t dstIdx) noexcept {
auto const c = color(m_pal, m_page, srcIdx);
std::ignore = colors(m_pal, m_page).erase(srcIdx);
colors(m_pal, m_page).emplace(dstIdx, c);
}
}

View File

@ -13,32 +13,35 @@
namespace nostalgia::core {
enum class PaletteEditorCommandId {
AddPage,
ApplyColorAllPages,
DuplicatePage,
RemovePage,
AddColor,
RemoveColor,
UpdateColorInfo,
UpdateColor,
MoveColor,
};
class AddPageCommand: public studio::UndoCommand {
class ApplyColorAllPagesCommand: public studio::UndoCommand {
private:
Palette &m_pal;
size_t const m_page{};
size_t const m_idx{};
ox::Vector<Color16> const m_origColors;
public:
AddPageCommand(Palette &pal) noexcept;
ApplyColorAllPagesCommand(Palette &pal, size_t page, size_t idx);
~AddPageCommand() noexcept override = default;
~ApplyColorAllPagesCommand() noexcept override = default;
[[nodiscard]]
int commandId() const noexcept final;
void redo() noexcept final;
void undo() noexcept final;
ox::Error redo() noexcept final;
ox::Error undo() noexcept final;
};
class DuplicatePageCommand: public studio::UndoCommand {
@ -55,9 +58,9 @@ class DuplicatePageCommand: public studio::UndoCommand {
[[nodiscard]]
int commandId() const noexcept final;
void redo() noexcept final;
ox::Error redo() noexcept final;
void undo() noexcept final;
ox::Error undo() noexcept final;
[[nodiscard]]
size_t insertIdx() const noexcept;
@ -78,88 +81,121 @@ class RemovePageCommand: public studio::UndoCommand {
[[nodiscard]]
int commandId() const noexcept final;
void redo() noexcept final;
ox::Error redo() noexcept final;
void undo() noexcept final;
ox::Error undo() noexcept final;
};
class AddColorCommand: public studio::UndoCommand {
private:
Palette *m_pal = nullptr;
Palette &m_pal;
Color16 m_color = 0;
int m_idx = -1;
size_t m_page = 0;
size_t const m_idx = 0;
public:
AddColorCommand(Palette *pal, Color16 color, size_t page, int idx) noexcept;
AddColorCommand(Palette &pal, Color16 color, size_t idx) noexcept;
~AddColorCommand() noexcept override = default;
[[nodiscard]]
int commandId() const noexcept override;
void redo() noexcept override;
ox::Error redo() noexcept override;
void undo() noexcept override;
ox::Error undo() noexcept override;
};
class RemoveColorCommand: public studio::UndoCommand {
private:
Palette *m_pal = nullptr;
Color16 m_color = 0;
size_t m_page = 0;
int m_idx = -1;
Palette &m_pal;
size_t const m_idx = 0;
ox::Vector<Color16> const m_colors;
public:
RemoveColorCommand(Palette *pal, Color16 color, size_t page, int idx) noexcept;
RemoveColorCommand(Palette &pal, size_t idx) noexcept;
~RemoveColorCommand() noexcept override = default;
[[nodiscard]]
int commandId() const noexcept override;
void redo() noexcept override;
ox::Error redo() noexcept override;
void undo() noexcept override;
ox::Error undo() noexcept override;
};
class UpdateColorInfoCommand: public studio::UndoCommand {
private:
Palette &m_pal;
size_t const m_idx{};
Palette::ColorInfo m_altColorInfo;
public:
UpdateColorInfoCommand(
Palette &pal,
size_t idx,
Palette::ColorInfo newColorInfo);
~UpdateColorInfoCommand() noexcept override = default;
[[nodiscard]]
bool mergeWith(const UndoCommand &cmd) noexcept final;
[[nodiscard]]
int commandId() const noexcept final;
ox::Error redo() noexcept final;
ox::Error undo() noexcept final;
private:
void swap() noexcept;
};
class UpdateColorCommand: public studio::UndoCommand {
private:
Palette *m_pal = nullptr;
Color16 m_oldColor = 0;
Color16 m_newColor = 0;
size_t m_page = 0;
int m_idx = -1;
Palette &m_pal;
size_t const m_page = 0;
size_t const m_idx{};
Color16 m_altColor{};
public:
UpdateColorCommand(Palette *pal, size_t page, int idx, Color16 oldColor, Color16 newColor) noexcept;
UpdateColorCommand(
Palette &pal,
size_t page,
size_t idx,
Color16 newColor);
~UpdateColorCommand() noexcept override = default;
[[nodiscard]]
bool mergeWith(const UndoCommand *cmd) noexcept final;
bool mergeWith(const UndoCommand &cmd) noexcept final;
[[nodiscard]]
int commandId() const noexcept final;
void redo() noexcept final;
ox::Error redo() noexcept final;
void undo() noexcept final;
ox::Error undo() noexcept final;
private:
void swap() noexcept;
};
class MoveColorCommand: public studio::UndoCommand {
private:
Palette *m_pal = nullptr;
size_t m_page = 0;
std::size_t m_idx = 0;
int m_offset = 0;
Palette &m_pal;
size_t const m_page = 0;
std::size_t const m_srcIdx = 0;
std::size_t const m_dstIdx = 0;
public:
MoveColorCommand(Palette *pal, size_t page, std::size_t idx, int offset) noexcept;
MoveColorCommand(Palette &pal, size_t page, size_t srcIdx, size_t dstIdx) noexcept;
~MoveColorCommand() noexcept override = default;
@ -167,12 +203,12 @@ class MoveColorCommand: public studio::UndoCommand {
int commandId() const noexcept override;
public:
void redo() noexcept override;
ox::Error redo() noexcept override;
void undo() noexcept override;
ox::Error undo() noexcept override;
private:
void moveColor(int idx, int offset) noexcept;
void moveColor(size_t srcIdx, size_t dstIdx) noexcept;
};
}

View File

@ -24,7 +24,7 @@ AddSubSheetCommand::AddSubSheetCommand(
}
}
void AddSubSheetCommand::redo() noexcept {
ox::Error AddSubSheetCommand::redo() noexcept {
auto &parent = getSubSheet(m_img, m_parentIdx);
if (m_addedSheets.size() < 2) {
auto i = parent.subsheets.size();
@ -35,9 +35,10 @@ void AddSubSheetCommand::redo() noexcept {
parent.columns = 0;
parent.subsheets.emplace_back(m_img.idIt++, "Subsheet 1", 1, 1, m_img.bpp);
}
return {};
}
void AddSubSheetCommand::undo() noexcept {
ox::Error AddSubSheetCommand::undo() noexcept {
auto &parent = getSubSheet(m_img, m_parentIdx);
if (parent.subsheets.size() == 2) {
auto s = parent.subsheets[0];
@ -47,9 +48,10 @@ void AddSubSheetCommand::undo() noexcept {
parent.subsheets.clear();
} else {
for (auto idx = m_addedSheets.rbegin(); idx != m_addedSheets.rend(); ++idx) {
oxLogError(rmSubSheet(m_img, *idx));
oxReturnError(rmSubSheet(m_img, *idx));
}
}
return {};
}
int AddSubSheetCommand::commandId() const noexcept {

View File

@ -17,9 +17,9 @@ class AddSubSheetCommand: public TileSheetCommand {
public:
AddSubSheetCommand(TileSheet &img, TileSheet::SubSheetIdx parentIdx) noexcept;
void redo() noexcept final;
ox::Error redo() noexcept final;
void undo() noexcept final;
ox::Error undo() noexcept final;
[[nodiscard]]
int commandId() const noexcept final;

View File

@ -41,18 +41,20 @@ CutPasteCommand::CutPasteCommand(
}
}
void CutPasteCommand::redo() noexcept {
ox::Error CutPasteCommand::redo() noexcept {
auto &subsheet = getSubSheet(m_img, m_subSheetIdx);
for (const auto &c : m_changes) {
setPixel(subsheet, m_img.bpp, c.idx, static_cast<uint8_t>(c.newPalIdx));
}
return {};
}
void CutPasteCommand::undo() noexcept {
ox::Error CutPasteCommand::undo() noexcept {
auto &subsheet = getSubSheet(m_img, m_subSheetIdx);
for (const auto &c : m_changes) {
setPixel(subsheet, m_img.bpp, c.idx, static_cast<uint8_t>(c.oldPalIdx));
}
return {};
}
int CutPasteCommand::commandId() const noexcept {

View File

@ -69,9 +69,9 @@ class CutPasteCommand: public TileSheetCommand {
ox::Point const&dstEnd,
TileSheetClipboard const&cb) noexcept;
void redo() noexcept final;
ox::Error redo() noexcept final;
void undo() noexcept final;
ox::Error undo() noexcept final;
[[nodiscard]]
int commandId() const noexcept final;

View File

@ -28,7 +28,7 @@ core::DeleteTilesCommand::DeleteTilesCommand(
}
}
void core::DeleteTilesCommand::redo() noexcept {
ox::Error core::DeleteTilesCommand::redo() noexcept {
auto &s = getSubSheet(m_img, m_idx);
auto &p = s.pixels;
auto srcPos = m_deletePos + m_deleteSz;
@ -37,9 +37,10 @@ void core::DeleteTilesCommand::redo() noexcept {
const auto dst2 = p.data() + (p.size() - m_deleteSz);
ox::memmove(dst1, src, p.size() - srcPos);
ox::memset(dst2, 0, m_deleteSz * sizeof(decltype(p[0])));
return {};
}
void DeleteTilesCommand::undo() noexcept {
ox::Error DeleteTilesCommand::undo() noexcept {
auto &s = getSubSheet(m_img, m_idx);
auto &p = s.pixels;
const auto src = p.data() + m_deletePos;
@ -48,6 +49,7 @@ void DeleteTilesCommand::undo() noexcept {
const auto sz = p.size() - m_deletePos - m_deleteSz;
ox::memmove(dst1, src, sz);
ox::memcpy(dst2, m_deletedPixels.data(), m_deletedPixels.size());
return {};
}
int DeleteTilesCommand::commandId() const noexcept {

View File

@ -23,9 +23,9 @@ class DeleteTilesCommand: public TileSheetCommand {
std::size_t tileIdx,
std::size_t tileCnt) noexcept;
void redo() noexcept final;
ox::Error redo() noexcept final;
void undo() noexcept final;
ox::Error undo() noexcept final;
[[nodiscard]]
int commandId() const noexcept final;

View File

@ -56,18 +56,20 @@ bool DrawCommand::append(const ox::Vector<std::size_t> &idxList) noexcept {
return out;
}
void DrawCommand::redo() noexcept {
ox::Error DrawCommand::redo() noexcept {
auto &subsheet = getSubSheet(m_img, m_subSheetIdx);
for (const auto &c : m_changes) {
setPixel(subsheet, m_img.bpp, c.idx, static_cast<uint8_t>(m_palIdx));
}
return {};
}
void DrawCommand::undo() noexcept {
ox::Error DrawCommand::undo() noexcept {
auto &subsheet = getSubSheet(m_img, m_subSheetIdx);
for (const auto &c : m_changes) {
setPixel(subsheet, m_img.bpp, c.idx, static_cast<uint8_t>(c.oldPalIdx));
}
return {};
}
int DrawCommand::commandId() const noexcept {

View File

@ -40,9 +40,9 @@ class DrawCommand: public TileSheetCommand {
bool append(const ox::Vector<std::size_t> &idxList) noexcept;
void redo() noexcept final;
ox::Error redo() noexcept final;
void undo() noexcept final;
ox::Error undo() noexcept final;
[[nodiscard]]
int commandId() const noexcept final;

View File

@ -28,7 +28,7 @@ core::InsertTilesCommand::InsertTilesCommand(
}
}
void InsertTilesCommand::redo() noexcept {
ox::Error InsertTilesCommand::redo() noexcept {
auto &s = getSubSheet(m_img, m_idx);
auto &p = s.pixels;
auto dstPos = m_insertPos + m_insertCnt;
@ -36,9 +36,10 @@ void InsertTilesCommand::redo() noexcept {
const auto src = p.data() + m_insertPos;
ox::memmove(dst, src, p.size() - dstPos);
ox::memset(src, 0, m_insertCnt * sizeof(decltype(p[0])));
return {};
}
void InsertTilesCommand::undo() noexcept {
ox::Error InsertTilesCommand::undo() noexcept {
auto &s = getSubSheet(m_img, m_idx);
auto &p = s.pixels;
const auto srcIdx = m_insertPos + m_insertCnt;
@ -48,6 +49,7 @@ void InsertTilesCommand::undo() noexcept {
const auto sz = p.size() - srcIdx;
ox::memmove(dst1, src, sz);
ox::memcpy(dst2, m_deletedPixels.data(), m_deletedPixels.size());
return {};
}
int InsertTilesCommand::commandId() const noexcept {

View File

@ -23,9 +23,9 @@ class InsertTilesCommand: public TileSheetCommand {
std::size_t tileIdx,
std::size_t tileCnt) noexcept;
void redo() noexcept final;
ox::Error redo() noexcept final;
void undo() noexcept final;
ox::Error undo() noexcept final;
[[nodiscard]]
int commandId() const noexcept final;

View File

@ -16,12 +16,14 @@ core::PaletteChangeCommand::PaletteChangeCommand(
m_newPalette(ox::FileAddress(ox::sfmt<ox::IString<43>>("uuid://{}", newPalette))) {
}
void PaletteChangeCommand::redo() noexcept {
ox::Error PaletteChangeCommand::redo() noexcept {
m_img.defaultPalette = m_newPalette;
return {};
}
void PaletteChangeCommand::undo() noexcept {
ox::Error PaletteChangeCommand::undo() noexcept {
m_img.defaultPalette = m_oldPalette;
return {};
}
int PaletteChangeCommand::commandId() const noexcept {

View File

@ -21,9 +21,9 @@ class PaletteChangeCommand: public TileSheetCommand {
TileSheet &img,
ox::CRStringView newPalette) noexcept;
void redo() noexcept final;
ox::Error redo() noexcept final;
void undo() noexcept final;
ox::Error undo() noexcept final;
[[nodiscard]]
int commandId() const noexcept final;

View File

@ -11,19 +11,20 @@ core::RmSubSheetCommand::RmSubSheetCommand(TileSheet &img, TileSheet::SubSheetId
m_idx(std::move(idx)),
m_parentIdx(m_idx) {
m_parentIdx.pop_back();
auto &parent = getSubSheet(m_img, m_parentIdx);
m_sheet = parent.subsheets[*m_idx.back().value];
}
void RmSubSheetCommand::redo() noexcept {
ox::Error RmSubSheetCommand::redo() noexcept {
auto &parent = getSubSheet(m_img, m_parentIdx);
oxLogError(parent.subsheets.erase(*m_idx.back().value).error);
m_sheet = std::move(parent.subsheets[*m_idx.back().value]);
oxReturnError(parent.subsheets.erase(*m_idx.back().value).error);
return {};
}
void RmSubSheetCommand::undo() noexcept {
ox::Error RmSubSheetCommand::undo() noexcept {
auto &parent = getSubSheet(m_img, m_parentIdx);
auto i = *m_idx.back().value;
parent.subsheets.insert(i, m_sheet);
auto const i = *m_idx.back().value;
parent.subsheets.insert(i, std::move(m_sheet));
return {};
}
int RmSubSheetCommand::commandId() const noexcept {

View File

@ -18,9 +18,9 @@ class RmSubSheetCommand: public TileSheetCommand {
public:
RmSubSheetCommand(TileSheet &img, TileSheet::SubSheetIdx idx) noexcept;
void redo() noexcept final;
ox::Error redo() noexcept final;
void undo() noexcept final;
ox::Error undo() noexcept final;
[[nodiscard]]
int commandId() const noexcept final;

View File

@ -20,17 +20,17 @@ core::UpdateSubSheetCommand::UpdateSubSheetCommand(
m_newRows(rows) {
}
void UpdateSubSheetCommand::redo() noexcept {
ox::Error UpdateSubSheetCommand::redo() noexcept {
auto &sheet = getSubSheet(m_img, m_idx);
sheet.name = m_newName;
sheet.columns = m_newCols;
sheet.rows = m_newRows;
oxLogError(setPixelCount(sheet, m_img.bpp, static_cast<std::size_t>(PixelsPerTile * m_newCols * m_newRows)));
oxLogError(resizeSubsheet(sheet, m_img.bpp, {m_newCols, m_newRows}));
return {};
}
void UpdateSubSheetCommand::undo() noexcept {
ox::Error UpdateSubSheetCommand::undo() noexcept {
auto &sheet = getSubSheet(m_img, m_idx);
sheet = m_sheet;
return {};
}
int UpdateSubSheetCommand::commandId() const noexcept {

View File

@ -25,9 +25,9 @@ class UpdateSubSheetCommand: public TileSheetCommand {
int cols,
int rows) noexcept;
void redo() noexcept final;
ox::Error redo() noexcept final;
void undo() noexcept final;
ox::Error undo() noexcept final;
[[nodiscard]]
int commandId() const noexcept final;

View File

@ -7,6 +7,7 @@
#include <ox/std/point.hpp>
#include <keel/media.hpp>
#include <studio/studio.hpp>
#include "tilesheeteditor-imgui.hpp"
@ -14,6 +15,36 @@ namespace nostalgia::core {
namespace ig = studio::ig;
static ox::String configName(ox::StringView str) noexcept {
auto out = ox::String{str};
for (auto &c : out) {
if (c == '/' || c == '\\') {
c = '%';
}
}
return out;
}
struct TileSheetEditorConfig {
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.studio.TileSheetEditorConfig";
static constexpr auto TypeVersion = 1;
TileSheet::SubSheetIdx activeSubsheet{};
};
oxModelBegin(TileSheetEditorConfig)
oxModelFieldRename(activeSubsheet, active_subsheet)
oxModelEnd()
struct TileSheetEditorConfigs {
static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.studio.TileSheetEditorConfigs";
static constexpr auto TypeVersion = 1;
ox::HashMap<ox::String, TileSheetEditorConfig> configs;
};
oxModelBegin(TileSheetEditorConfigs)
oxModelField(configs)
oxModelEnd()
static ox::Vector<uint32_t> normalizePixelSizes(
ox::Vector<uint8_t> const&inPixels,
int const bpp) noexcept {
@ -75,7 +106,7 @@ static ox::Error toPngFile(
8)));
}
TileSheetEditorImGui::TileSheetEditorImGui(studio::StudioContext &sctx, ox::CRStringView path):
TileSheetEditorImGui::TileSheetEditorImGui(studio::StudioContext &sctx, ox::StringView const path):
Editor(path),
m_sctx(sctx),
m_tctx(m_sctx.tctx),
@ -87,6 +118,12 @@ TileSheetEditorImGui::TileSheetEditorImGui(studio::StudioContext &sctx, ox::CRSt
m_subsheetEditor.inputSubmitted.connect(this, &TileSheetEditorImGui::updateActiveSubsheet);
m_exportMenu.inputSubmitted.connect(this, &TileSheetEditorImGui::exportSubhseetToPng);
m_model.paletteChanged.connect(this, &TileSheetEditorImGui::setPaletteSelection);
// load config
auto const&config = studio::readConfig<TileSheetEditorConfig>(
keelCtx(m_sctx), configName(itemPath()));
if (config.ok()) {
m_model.setActiveSubsheet(validateSubSheetIdx(m_model.img(), config.value.activeSubsheet));
}
}
void TileSheetEditorImGui::exportFile() {
@ -105,6 +142,10 @@ void TileSheetEditorImGui::paste() {
m_model.paste();
}
bool TileSheetEditorImGui::acceptsClipboardPayload() const noexcept {
return m_model.acceptsClipboardPayload();
}
void TileSheetEditorImGui::keyStateChanged(turbine::Key key, bool down) {
if (!down) {
return;
@ -116,20 +157,20 @@ void TileSheetEditorImGui::keyStateChanged(turbine::Key key, bool down) {
auto const popupOpen = m_subsheetEditor.isOpen() && m_exportMenu.isOpen();
auto const pal = m_model.pal();
if (!popupOpen) {
auto const colorCnt = pal.pages[m_model.palettePage()].size();
auto const colorCnt = core::colorCnt(pal, m_model.palettePage());
if (key == turbine::Key::Alpha_D) {
m_tool = Tool::Draw;
m_tool = TileSheetTool::Draw;
setCopyEnabled(false);
setCutEnabled(false);
setPasteEnabled(false);
m_model.clearSelection();
} else if (key == turbine::Key::Alpha_S) {
m_tool = Tool::Select;
m_tool = TileSheetTool::Select;
setCopyEnabled(true);
setCutEnabled(true);
setPasteEnabled(true);
} else if (key == turbine::Key::Alpha_F) {
m_tool = Tool::Fill;
m_tool = TileSheetTool::Fill;
setCopyEnabled(false);
setCutEnabled(false);
setPasteEnabled(false);
@ -140,7 +181,8 @@ void TileSheetEditorImGui::keyStateChanged(turbine::Key key, bool down) {
static_cast<uint32_t>(key - turbine::Key::Num_1), m_model.pal().pages.size() - 1);
m_model.setPalettePage(idx);
} else if (key <= turbine::Key::Num_0 + colorCnt) {
auto const idx = ox::min<std::size_t>(static_cast<uint32_t>(key - turbine::Key::Num_1), colorCnt - 1);
auto const idx = ox::min<std::size_t>(
static_cast<uint32_t>(key - turbine::Key::Num_1), colorCnt - 1);
m_view.setPalIdx(idx);
}
} else if (key == turbine::Key::Num_0) {
@ -173,17 +215,17 @@ void TileSheetEditorImGui::draw(studio::StudioContext&) noexcept {
ImGui::BeginChild("ToolBox", ImVec2(m_palViewWidth - 24, 30), true);
{
auto const btnSz = ImVec2(45, 14);
if (ImGui::Selectable("Select", m_tool == Tool::Select, 0, btnSz)) {
m_tool = Tool::Select;
if (ImGui::Selectable("Select", m_tool == TileSheetTool::Select, 0, btnSz)) {
m_tool = TileSheetTool::Select;
}
ImGui::SameLine();
if (ImGui::Selectable("Draw", m_tool == Tool::Draw, 0, btnSz)) {
m_tool = Tool::Draw;
if (ImGui::Selectable("Draw", m_tool == TileSheetTool::Draw, 0, btnSz)) {
m_tool = TileSheetTool::Draw;
m_model.clearSelection();
}
ImGui::SameLine();
if (ImGui::Selectable("Fill", m_tool == Tool::Fill, 0, btnSz)) {
m_tool = Tool::Fill;
if (ImGui::Selectable("Fill", m_tool == TileSheetTool::Fill, 0, btnSz)) {
m_tool = TileSheetTool::Fill;
m_model.clearSelection();
}
}
@ -204,7 +246,7 @@ void TileSheetEditorImGui::draw(studio::StudioContext&) noexcept {
auto const&parent = m_model.activeSubSheet();
m_model.addSubsheet(insertOnIdx);
insertOnIdx.emplace_back(parent.subsheets.size() - 1);
m_model.setActiveSubsheet(insertOnIdx);
setActiveSubsheet(insertOnIdx);
}
ImGui::SameLine();
if (ig::PushButton("-", btnSize)) {
@ -237,7 +279,7 @@ void TileSheetEditorImGui::draw(studio::StudioContext&) noexcept {
}
ImGui::EndChild();
m_subsheetEditor.draw(m_tctx);
m_exportMenu.draw(m_sctx);
m_exportMenu.draw(m_tctx);
}
void TileSheetEditorImGui::drawSubsheetSelector(
@ -257,7 +299,7 @@ void TileSheetEditorImGui::drawSubsheetSelector(
auto const open = ImGui::TreeNodeEx(lbl.c_str(), flags);
ImGui::SameLine();
if (ImGui::IsItemClicked()) {
m_model.setActiveSubsheet(path);
setActiveSubsheet(path);
}
if (ImGui::IsMouseDoubleClicked(0) && ImGui::IsItemHovered()) {
showSubsheetEditor();
@ -370,16 +412,16 @@ void TileSheetEditorImGui::drawTileSheet(ox::Vec2 const&fbSize) noexcept {
if (io.MouseDown[0] && m_prevMouseDownPos != mousePos) {
m_prevMouseDownPos = mousePos;
switch (m_tool) {
case Tool::Draw:
case TileSheetTool::Draw:
m_view.clickDraw(fbSize, clickPos(winPos, mousePos));
break;
case Tool::Fill:
case TileSheetTool::Fill:
m_view.clickFill(fbSize, clickPos(winPos, mousePos));
break;
case Tool::Select:
case TileSheetTool::Select:
m_view.clickSelect(fbSize, clickPos(winPos, mousePos));
break;
case Tool::None:
case TileSheetTool::None:
break;
}
}
@ -396,7 +438,7 @@ void TileSheetEditorImGui::drawTileSheet(ox::Vec2 const&fbSize) noexcept {
}
if (io.MouseReleased[0]) {
m_prevMouseDownPos = {-1, -1};
m_view.releaseMouseButton();
m_view.releaseMouseButton(m_tool);
}
}
@ -428,31 +470,39 @@ void TileSheetEditorImGui::drawPaletteSelector() noexcept {
ImGui::Indent(-20);
}
// header
if (ImGui::BeginTable("PaletteTable", 3, ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchProp)) {
ImGui::TableSetupColumn("No.", 0, 0.45f);
if (ImGui::BeginTable(
"PaletteTable", 4, ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchProp)) {
ImGui::TableSetupColumn("Idx", 0, 0.6f);
ImGui::TableSetupColumn("", 0, 0.22f);
ImGui::TableSetupColumn("Name", 0, 3);
ImGui::TableSetupColumn("Color16", 0, 3);
ImGui::TableHeadersRow();
{
auto const&pal = m_model.pal();
for (auto i = 0u; auto c: pal.pages[m_model.palettePage()]) {
ImGui::PushID(static_cast<int>(i));
// Column: color idx
ImGui::TableNextColumn();
auto const label = ox::itoa(i + 1);
auto const rowSelected = i == m_view.palIdx();
if (ImGui::Selectable(label.c_str(), rowSelected, ImGuiSelectableFlags_SpanAllColumns)) {
m_view.setPalIdx(i);
if (pal.pages.size() > m_model.palettePage()) {
for (auto i = 0u; auto const&c: pal.pages[m_model.palettePage()]) {
ImGui::PushID(static_cast<int>(i));
// Column: color idx
ImGui::TableNextColumn();
auto const label = ox::itoa(i + 1);
auto const rowSelected = i == m_view.palIdx();
if (ImGui::Selectable(
label.c_str(), rowSelected, ImGuiSelectableFlags_SpanAllColumns)) {
m_view.setPalIdx(i);
}
// Column: color RGB
ImGui::TableNextColumn();
auto ic = ImGui::GetColorU32(ImVec4(redf(c), greenf(c), bluef(c), 1));
ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, ic);
ImGui::TableNextColumn();
auto const&name = pal.colorInfo[i].name;
ImGui::Text("%s", name.c_str());
ImGui::TableNextColumn();
ImGui::Text("(%02d, %02d, %02d)", red16(c), green16(c), blue16(c));
ImGui::TableNextRow();
ImGui::PopID();
++i;
}
// Column: color RGB
ImGui::TableNextColumn();
auto ic = ImGui::GetColorU32(ImVec4(redf(c), greenf(c), bluef(c), 1));
ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, ic);
ImGui::TableNextColumn();
ImGui::Text("(%02d, %02d, %02d)", red16(c), green16(c), blue16(c));
ImGui::TableNextRow();
ImGui::PopID();
++i;
}
}
ImGui::EndTable();
@ -476,12 +526,20 @@ ox::Error TileSheetEditorImGui::setPaletteSelection() noexcept {
return {};
}
void TileSheetEditorImGui::setActiveSubsheet(TileSheet::SubSheetIdx path) noexcept {
m_model.setActiveSubsheet(path);
studio::editConfig<TileSheetEditorConfig>(keelCtx(m_sctx), configName(itemPath()),
[&path](TileSheetEditorConfig *config) {
config->activeSubsheet = std::move(path);
});
}
ox::Error TileSheetEditorImGui::markUnsavedChanges(studio::UndoCommand const*) noexcept {
setUnsavedChanges(true);
return {};
}
void TileSheetEditorImGui::SubSheetEditor::draw(turbine::Context &sctx) noexcept {
void TileSheetEditorImGui::SubSheetEditor::draw(turbine::Context &tctx) noexcept {
constexpr auto popupName = "Edit Subsheet";
if (!m_show) {
return;
@ -490,7 +548,7 @@ void TileSheetEditorImGui::SubSheetEditor::draw(turbine::Context &sctx) noexcept
auto constexpr popupWidth = 235.f;
auto const popupHeight = modSize ? 130.f : 85.f;
auto const popupSz = ImVec2(popupWidth, popupHeight);
if (ig::BeginPopup(sctx, popupName, m_show, popupSz)) {
if (ig::BeginPopup(tctx, popupName, m_show, popupSz)) {
ImGui::InputText("Name", m_name.data(), m_name.cap());
if (modSize) {
ImGui::InputInt("Columns", &m_cols);
@ -507,7 +565,7 @@ void TileSheetEditorImGui::SubSheetEditor::close() noexcept {
m_show = false;
}
void TileSheetEditorImGui::ExportMenu::draw(studio::StudioContext &sctx) noexcept {
void TileSheetEditorImGui::ExportMenu::draw(turbine::Context &tctx) noexcept {
constexpr auto popupName = "Export Tile Sheet";
if (!m_show) {
return;
@ -515,7 +573,7 @@ void TileSheetEditorImGui::ExportMenu::draw(studio::StudioContext &sctx) noexcep
constexpr auto popupWidth = 235.f;
constexpr auto popupHeight = 85.f;
constexpr auto popupSz = ImVec2(popupWidth, popupHeight);
if (ig::BeginPopup(sctx.tctx, popupName, m_show, popupSz)) {
if (ig::BeginPopup(tctx, popupName, m_show, popupSz)) {
ImGui::InputInt("Scale", &m_scale);
m_scale = ox::clamp(m_scale, 1, 50);
if (ig::PopupControlsOkCancel(popupWidth, m_show) == ig::PopupResponse::OK) {

View File

@ -16,13 +16,6 @@
namespace nostalgia::core {
enum class Tool {
None,
Draw,
Fill,
Select,
};
class TileSheetEditorImGui: public studio::Editor {
private:
@ -55,7 +48,7 @@ class TileSheetEditorImGui: public studio::Editor {
m_show = true;
m_scale = 5;
}
void draw(studio::StudioContext &sctx) noexcept;
void draw(turbine::Context &sctx) noexcept;
void close() noexcept;
[[nodiscard]]
inline bool isOpen() const noexcept { return m_show; }
@ -71,10 +64,10 @@ class TileSheetEditorImGui: public studio::Editor {
TileSheetEditorModel &m_model;
float m_palViewWidth = 300;
ox::Vec2 m_prevMouseDownPos;
Tool m_tool = Tool::Draw;
TileSheetTool m_tool = TileSheetTool::Draw;
public:
TileSheetEditorImGui(studio::StudioContext &sctx, ox::CRStringView path);
TileSheetEditorImGui(studio::StudioContext &sctx, ox::StringView path);
~TileSheetEditorImGui() override = default;
@ -86,6 +79,9 @@ class TileSheetEditorImGui: public studio::Editor {
void paste() override;
[[nodiscard]]
bool acceptsClipboardPayload() const noexcept override;
void keyStateChanged(turbine::Key key, bool down) override;
void draw(studio::StudioContext&) noexcept override;
@ -115,6 +111,8 @@ class TileSheetEditorImGui: public studio::Editor {
private:
ox::Error markUnsavedChanges(studio::UndoCommand const*) noexcept;
void setActiveSubsheet(TileSheet::SubSheetIdx path) noexcept;
};
}

View File

@ -27,7 +27,8 @@
namespace nostalgia::core {
Palette const TileSheetEditorModel::s_defaultPalette = {
.pages = {ox::Vector<Color16>(128)},
.colorInfo = {ox::Vector<Palette::ColorInfo>{{}}},
.pages = {{ox::Vector<Color16>(128)}},
};
// delete pixels of all non-leaf nodes
@ -55,55 +56,63 @@ TileSheetEditorModel::TileSheetEditorModel(studio::StudioContext &sctx, ox::Stri
}
void TileSheetEditorModel::cut() {
if (!m_selection) {
return;
}
TileSheetClipboard blankCb;
auto cb = ox::make_unique<TileSheetClipboard>();
const auto &s = activeSubSheet();
for (int y = m_selectionBounds.y; y <= m_selectionBounds.y2(); ++y) {
for (int x = m_selectionBounds.x; x <= m_selectionBounds.x2(); ++x) {
auto pt = ox::Point(x, y);
const auto idx = core::idx(s, pt);
const auto c = getPixel(s, m_img.bpp, idx);
pt.x -= m_selectionBounds.x;
pt.y -= m_selectionBounds.y;
cb->addPixel(pt, c);
blankCb.addPixel(pt, 0);
}
}
const auto pt1 = m_selectionOrigin == ox::Point(-1, -1) ? ox::Point(0, 0) : m_selectionOrigin;
const auto pt2 = ox::Point(s.columns * TileWidth, s.rows * TileHeight);
auto const&s = activeSubSheet();
iterateSelectionRows(*m_selection, [&](int x, int y) {
auto pt = ox::Point{x, y};
auto const idx = core::idx(s, pt);
auto const c = getPixel(s, m_img.bpp, idx);
pt -= m_selection->a;
cb->addPixel(pt, c);
blankCb.addPixel(pt, 0);
});
auto const pt1 = m_selection->a;
auto const pt2 = ox::Point{s.columns * TileWidth, s.rows * TileHeight};
turbine::setClipboardObject(m_tctx, std::move(cb));
pushCommand(ox::make<CutPasteCommand>(CommandId::Cut, m_img, m_activeSubsSheetIdx, pt1, pt2, blankCb));
}
void TileSheetEditorModel::copy() {
auto cb = ox::make_unique<TileSheetClipboard>();
for (int y = m_selectionBounds.y; y <= m_selectionBounds.y2(); ++y) {
for (int x = m_selectionBounds.x; x <= m_selectionBounds.x2(); ++x) {
auto pt = ox::Point(x, y);
const auto &s = activeSubSheet();
const auto idx = core::idx(s, pt);
const auto c = getPixel(s, m_img.bpp, idx);
pt.x -= m_selectionBounds.x;
pt.y -= m_selectionBounds.y;
cb->addPixel(pt, c);
}
if (!m_selection) {
return;
}
auto cb = ox::make_unique<TileSheetClipboard>();
iterateSelectionRows(*m_selection, [&](int x, int y) {
auto pt = ox::Point{x, y};
const auto&s = activeSubSheet();
const auto idx = core::idx(s, pt);
const auto c = getPixel(s, m_img.bpp, idx);
pt -= m_selection->a;
cb->addPixel(pt, c);
});
turbine::setClipboardObject(m_tctx, std::move(cb));
}
void TileSheetEditorModel::paste() {
if (!m_selection) {
return;
}
auto [cb, err] = turbine::getClipboardObject<TileSheetClipboard>(m_tctx);
if (err) {
oxLogError(err);
oxErrf("Could not read clipboard: {}", toStr(err));
return;
}
const auto &s = activeSubSheet();
const auto pt1 = m_selectionOrigin == ox::Point(-1, -1) ? ox::Point(0, 0) : m_selectionOrigin;
const auto pt2 = ox::Point(s.columns * TileWidth, s.rows * TileHeight);
auto const&s = activeSubSheet();
auto const pt1 = m_selection->a;
auto const pt2 = ox::Point{s.columns * TileWidth, s.rows * TileHeight};
pushCommand(ox::make<CutPasteCommand>(CommandId::Paste, m_img, m_activeSubsSheetIdx, pt1, pt2, *cb));
}
bool TileSheetEditorModel::acceptsClipboardPayload() const noexcept {
auto const cb = getClipboardObject<TileSheetClipboard>(m_tctx);
return cb.ok();
}
ox::StringView TileSheetEditorModel::palPath() const noexcept {
auto [path, err] = m_img.defaultPalette.getPath();
if (err) {
@ -206,30 +215,26 @@ void TileSheetEditorModel::fill(ox::Point const&pt, int palIdx) noexcept {
}
void TileSheetEditorModel::select(ox::Point const&pt) noexcept {
if (!m_selectionOngoing) {
m_selectionOrigin = pt;
m_selectionOngoing = true;
m_selectionBounds = {pt, pt};
m_updated = true;
} else if (m_selectionBounds.pt2() != pt) {
m_selectionBounds = {m_selectionOrigin, pt};
if (m_selTracker.updateCursorPoint(pt)) {
m_selection.emplace(m_selTracker.selection());
m_updated = true;
}
}
void TileSheetEditorModel::completeSelection() noexcept {
m_selectionOngoing = false;
auto &s = activeSubSheet();
auto pt = m_selectionBounds.pt2();
pt.x = ox::min(s.columns * TileWidth, pt.x);
pt.y = ox::min(s.rows * TileHeight, pt.y);
m_selectionBounds.setPt2(pt);
if (m_selTracker.selectionOngoing()) {
m_selTracker.finishSelection();
m_selection.emplace(m_selTracker.selection());
auto&pt = m_selection->b;
auto&s = activeSubSheet();
pt.x = ox::min(s.columns * TileWidth - 1, pt.x);
pt.y = ox::min(s.rows * TileHeight - 1, pt.y);
}
}
void TileSheetEditorModel::clearSelection() noexcept {
m_updated = true;
m_selectionOrigin = {-1, -1};
m_selectionBounds = {{-1, -1}, {-1, -1}};
m_selection.reset();
}
bool TileSheetEditorModel::updated() const noexcept {
@ -266,9 +271,9 @@ ox::Error TileSheetEditorModel::saveFile() noexcept {
}
bool TileSheetEditorModel::pixelSelected(std::size_t idx) const noexcept {
const auto &s = activeSubSheet();
const auto pt = idxToPt(static_cast<int>(idx), s.columns);
return m_selectionBounds.contains(pt);
auto const&s = activeSubSheet();
auto const pt = idxToPt(static_cast<int>(idx), s.columns);
return m_selection && m_selection->contains(pt);
}
void TileSheetEditorModel::getFillPixels(bool *pixels, ox::Point const&pt, int oldColor) const noexcept {
@ -305,7 +310,7 @@ void TileSheetEditorModel::getFillPixels(bool *pixels, ox::Point const&pt, int o
}
void TileSheetEditorModel::pushCommand(studio::UndoCommand *cmd) noexcept {
m_undoStack.push(ox::UPtr<studio::UndoCommand>(cmd));
std::ignore = m_undoStack.push(ox::UPtr<studio::UndoCommand>(cmd));
m_ongoingDrawCommand = dynamic_cast<DrawCommand*>(cmd);
m_updated = true;
}

View File

@ -33,10 +33,9 @@ class TileSheetEditorModel: public ox::SignalHandler {
size_t m_palettePage{};
studio::UndoStack &m_undoStack;
class DrawCommand *m_ongoingDrawCommand = nullptr;
studio::SelectionTracker m_selTracker;
ox::Optional<studio::Selection> m_selection;
bool m_updated = false;
bool m_selectionOngoing = false;
ox::Point m_selectionOrigin = {-1, -1};
ox::Bounds m_selectionBounds = {{-1, -1}, {-1, -1}};
public:
TileSheetEditorModel(studio::StudioContext &sctx, ox::StringView path, studio::UndoStack &undoStack);
@ -49,6 +48,9 @@ class TileSheetEditorModel: public ox::SignalHandler {
void paste();
[[nodiscard]]
bool acceptsClipboardPayload() const noexcept;
[[nodiscard]]
constexpr TileSheet const&img() const noexcept;

View File

@ -84,9 +84,18 @@ void TileSheetEditorView::clickFill(ox::Vec2 const&paneSize, ox::Vec2 const&clic
m_model.fill(pt, static_cast<int>(m_palIdx));
}
void TileSheetEditorView::releaseMouseButton() noexcept {
m_model.endDrawCommand();
m_model.completeSelection();
void TileSheetEditorView::releaseMouseButton(TileSheetTool tool) noexcept {
switch (tool) {
case TileSheetTool::Draw:
m_model.endDrawCommand();
break;
case TileSheetTool::Select:
m_model.completeSelection();
break;
case TileSheetTool::Fill:
case TileSheetTool::None:
break;
}
}
void TileSheetEditorView::resizeView(ox::Vec2 const&sz) noexcept {

View File

@ -18,7 +18,8 @@
namespace nostalgia::core {
enum class TileSheetTool: int {
enum class TileSheetTool {
None,
Select,
Draw,
Fill,
@ -33,6 +34,8 @@ constexpr auto toString(TileSheetTool t) noexcept {
return "Draw";
case TileSheetTool::Fill:
return "Fill";
case TileSheetTool::None:
return "None";
}
return "";
}
@ -66,7 +69,7 @@ class TileSheetEditorView: public ox::SignalHandler {
void clickFill(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept;
void releaseMouseButton() noexcept;
void releaseMouseButton(TileSheetTool tool) noexcept;
void scrollV(ox::Vec2 const&paneSz, float wheel, bool zoomMod) noexcept;

View File

@ -139,10 +139,7 @@ void TileSheetPixels::setBufferObjects(ox::Vec2 const&paneSize) noexcept {
return;
}
if (m_model.pixelSelected(i)) {
auto const r = red16(color) / 2;
auto const g = (green16(color) + 20) / 2;
auto const b = (blue16(color) + 31) / 2;
color = color16(r, g, b);
color = applySelectionColor(color);
}
setPixelBufferObject(paneSize, static_cast<unsigned>(i * VertexVboRows), fx, fy, color, vbo, ebo);
});

View File

@ -15,11 +15,11 @@ std::size_t idx(TileSheet::SubSheet const&ss, ox::Point const&pt) noexcept {
}
[[nodiscard]]
static TileSheet::SubSheet const*getSubsheet(TileSheet::SubSheet const&ss, SubSheetId const id) noexcept {
static TileSheet::SubSheet const *getSubsheet(TileSheet::SubSheet const&ss, SubSheetId const id) noexcept {
if (ss.id == id) {
return &ss;
}
for (auto const&child : ss.subsheets) {
for (auto const&child: ss.subsheets) {
if (auto out = getSubsheet(child, id)) {
return out;
}
@ -34,7 +34,7 @@ static size_t getTileCnt(TileSheet::SubSheet const&ss, int const bpp) noexcept {
return ss.pixels.size() / bytesPerTile;
} else {
size_t out{};
for (auto const&child : ss.subsheets) {
for (auto const&child: ss.subsheets) {
out += getTileCnt(child, bpp);
}
return out;
@ -45,7 +45,7 @@ size_t getTileCnt(TileSheet const&ts) noexcept {
return getTileCnt(ts.subsheet, ts.bpp);
}
TileSheet::SubSheet const*getSubsheet(TileSheet const&ts, SubSheetId const id) noexcept {
TileSheet::SubSheet const *getSubsheet(TileSheet const&ts, SubSheetId const id) noexcept {
return getSubsheet(ts.subsheet, id);
}
@ -54,7 +54,7 @@ static ox::Optional<size_t> getPixelIdx(
SubSheetId const id,
size_t idx,
int8_t const bpp) noexcept {
for (auto const&child : ss.subsheets) {
for (auto const&child: ss.subsheets) {
if (child.id == id) {
return ox::Optional<size_t>(ox::in_place, idx);
}
@ -106,8 +106,8 @@ uint8_t getPixel(TileSheet::SubSheet const&ss, int8_t pBpp, ox::Point const&pt)
return getPixel(ss, pBpp, idx);
}
void setPixel(TileSheet::SubSheet &ss, int8_t pBpp, uint64_t idx, uint8_t palIdx) noexcept {
auto &pixel = ss.pixels[static_cast<std::size_t>(idx / 2)];
static void setPixel(ox::Vector<uint8_t> &pixels, int8_t pBpp, uint64_t idx, uint8_t palIdx) noexcept {
auto &pixel = pixels[static_cast<std::size_t>(idx / 2)];
if (pBpp == 4) {
if (idx & 1) {
pixel = static_cast<uint8_t>((pixel & 0b0000'1111) | (palIdx << 4));
@ -119,22 +119,39 @@ void setPixel(TileSheet::SubSheet &ss, int8_t pBpp, uint64_t idx, uint8_t palIdx
}
}
void setPixel(TileSheet::SubSheet &ss, int8_t pBpp, uint64_t idx, uint8_t palIdx) noexcept {
setPixel(ss.pixels, pBpp, idx, palIdx);
}
static void setPixel(ox::Vector<uint8_t> &pixels, int columns, int8_t pBpp, ox::Point const&pt, uint8_t palIdx) noexcept {
const auto idx = ptToIdx(pt, columns);
setPixel(pixels, pBpp, idx, palIdx);
}
void setPixel(TileSheet::SubSheet &ss, int8_t pBpp, ox::Point const&pt, uint8_t palIdx) noexcept {
const auto idx = ptToIdx(pt, ss.columns);
setPixel(ss, pBpp, idx, palIdx);
}
ox::Error setPixelCount(TileSheet::SubSheet &ss, int8_t pBpp, std::size_t cnt) noexcept {
static ox::Error setPixelCount(ox::Vector<uint8_t> &pixels, int8_t pBpp, std::size_t cnt) noexcept {
size_t sz{};
switch (pBpp) {
case 4:
ss.pixels.resize(cnt / 2);
return OxError(0);
sz = cnt / 2;
break;
case 8:
ss.pixels.resize(cnt);
return OxError(0);
sz = cnt;
break;
default:
return OxError(1, "Invalid pBpp used for TileSheet::SubSheet::setPixelCount");
}
pixels.reserve(sz);
pixels.resize(sz);
return {};
}
ox::Error setPixelCount(TileSheet::SubSheet &ss, int8_t pBpp, std::size_t cnt) noexcept {
return setPixelCount(ss.pixels, pBpp, cnt);
}
unsigned pixelCnt(TileSheet::SubSheet const&ss, int8_t pBpp) noexcept {
@ -142,6 +159,23 @@ unsigned pixelCnt(TileSheet::SubSheet const&ss, int8_t pBpp) noexcept {
return pBpp == 4 ? pixelsSize * 2 : pixelsSize;
}
ox::Error resizeSubsheet(TileSheet::SubSheet &ss, int8_t pBpp, ox::Size const&sz) noexcept {
ox::Vector<uint8_t> out;
oxReturnError(setPixelCount(out, pBpp, static_cast<size_t>(sz.width * sz.height) * PixelsPerTile));
auto const w = ss.columns * TileWidth;
auto const h = ss.rows * TileHeight;
for (auto x = 0; x < w; ++x) {
for (auto y = 0; y < h; ++y) {
auto const palIdx = getPixel(ss, pBpp, {x, y});
setPixel(out, sz.width, pBpp, {x, y}, palIdx);
}
}
ss.columns = sz.width;
ss.rows = sz.height;
ss.pixels = std::move(out);
return {};
}
ox::Result<ox::StringView> getNameFor(TileSheet::SubSheet const&ss, SubSheetId pId) noexcept {
if (ss.id == pId) {
return ox::StringView(ss.name);

View File

@ -50,7 +50,6 @@ void SceneEditorImGui::onActivated() noexcept {
ox::Error SceneEditorImGui::saveItem() noexcept {
const auto sctx = applicationData<studio::StudioContext>(m_ctx);
oxReturnError(sctx->project->writeObj(itemPath(), m_editor.scene()));
oxReturnError(keelCtx(m_ctx).assetManager.setAsset(itemPath(), m_editor.scene()));
return {};
}

View File

@ -4,6 +4,10 @@
#pragma once
#ifndef OX_BARE_METAL
#include <functional>
#endif
#include <ox/event/signal.hpp>
#include <ox/fs/fs.hpp>
#include <ox/model/typenamecatcher.hpp>
@ -178,9 +182,11 @@ constexpr AssetRef<T>::AssetRef(AssetRef &&h) noexcept: m_ctr(h.m_ctr) {
h.m_ctr = nullptr;
}
class Context;
class AssetManager {
private:
class AssetTypeManagerBase {
class AssetTypeManagerBase: public ox::SignalHandler {
public:
virtual ~AssetTypeManagerBase() = default;
@ -189,31 +195,28 @@ class AssetManager {
template<typename T>
class AssetTypeManager: public AssetTypeManagerBase {
public:
using Loader = std::function<ox::Result<T>(ox::StringView assetId)>;
private:
Loader m_loader{};
ox::HashMap<ox::String, ox::UPtr<AssetContainer<T>>> m_cache;
public:
ox::Result<AssetRef<T>> getAsset(ox::StringView const&assetId) const noexcept {
auto out = m_cache.at(assetId);
oxReturnError(out);
return AssetRef<T>(out.value->get());
}
AssetTypeManager(Loader loader) noexcept: m_loader(loader) {}
ox::Result<AssetRef<T>> setAsset(ox::StringView const&assetId, T const&obj) noexcept {
auto &p = m_cache[assetId];
if (!p) {
p = ox::make_unique<AssetContainer<T>>(obj);
} else {
p->set(obj);
p->updated.emit();
ox::Result<AssetRef<T>> getAsset(ox::StringView const assetId) const noexcept {
oxRequire(out, m_cache.at(assetId));
if (!out || !*out) {
return OxError(1, "asset is null");
}
return AssetRef<T>(p.get());
return AssetRef<T>(out->get());
}
ox::Result<AssetRef<T>> setAsset(ox::StringView const&assetId, T &&obj) noexcept {
ox::Result<AssetRef<T>> loadAsset(ox::StringView const assetId) noexcept {
auto &p = m_cache[assetId];
oxRequireM(obj, m_loader(assetId));
if (!p) {
p = ox::make_unique<AssetContainer<T>>(obj);
p = ox::make_unique<AssetContainer<T>>(std::move(obj));
} else {
p->set(std::move(obj));
p->updated.emit();
@ -221,6 +224,18 @@ class AssetManager {
return AssetRef<T>(p.get());
}
ox::Error reloadAsset(ox::StringView const assetId) noexcept {
auto &p = m_cache[assetId];
oxRequireM(obj, m_loader(assetId));
if (!p) {
p = ox::make_unique<AssetContainer<T>>(std::move(obj));
} else {
p->set(std::move(obj));
p->updated.emit();
}
return {};
}
void gc() noexcept final {
for (auto const&ack : m_cache.keys()) {
auto &ac = m_cache[ack];
@ -232,29 +247,48 @@ class AssetManager {
};
ox::HashMap<ox::String, ox::UPtr<AssetTypeManagerBase>> m_assetTypeManagers;
ox::HashMap<ox::String, ox::Signal<ox::Error(ox::StringView assetId)>> m_fileUpdated;
template<typename T>
AssetTypeManager<T> *getTypeManager() noexcept {
constexpr auto typeName = ox::requireModelTypeName<T>();
static_assert(ox::StringView(typeName) != "", "Types must have TypeName to use AssetManager");
auto &am = m_assetTypeManagers[typeName];
if (!am) {
am = ox::make_unique<AssetTypeManager<T>>();
ox::Result<AssetTypeManager<T>*> getTypeManager() noexcept {
constexpr auto &typeId = ox::ModelTypeId_v<T>;
static_assert(typeId != "", "Types must have TypeName to use AssetManager");
auto &am = m_assetTypeManagers[typeId];
auto const out = dynamic_cast<AssetTypeManager<T>*>(am.get());
if (!out) {
return OxError(1, "no AssetTypeManager for type");
}
return dynamic_cast<AssetTypeManager<T>*>(am.get());
return out;
}
public:
template<typename T>
ox::Result<AssetRef<T>> getAsset(ox::CRStringView assetId) noexcept {
auto m = getTypeManager<T>();
return m->getAsset(assetId);
void initTypeManager(auto const&makeLoader, Context &ctx) noexcept {
constexpr auto &typeId = ox::ModelTypeId_v<T>;
static_assert(typeId != "", "Types must have TypeName to use AssetManager");
auto &am = m_assetTypeManagers[typeId];
if (!am) {
am = ox::make_unique<AssetTypeManager<T>>(makeLoader(ctx));
}
}
template<typename T>
ox::Result<AssetRef<T>> setAsset(ox::CRStringView assetId, T const&obj) noexcept {
auto m = getTypeManager<T>();
return m->setAsset(assetId, obj);
ox::Result<AssetRef<T>> getAsset(ox::StringView assetId) noexcept {
oxRequire(m, getTypeManager<T>());
return m->getAsset(assetId);
}
ox::Error reloadAsset(ox::StringView assetId) noexcept {
m_fileUpdated[assetId].emit(assetId);
return {};
}
template<typename T>
ox::Result<AssetRef<T>> loadAsset(ox::StringView assetId) noexcept {
oxRequire(m, getTypeManager<T>());
oxRequire(out, m->loadAsset(assetId));
m_fileUpdated[assetId].connect(m, &AssetTypeManager<T>::reloadAsset);
return out;
}
void gc() noexcept {

View File

@ -32,14 +32,43 @@ oxModelEnd()
ox::Result<std::size_t> getPreloadAddr(keel::Context &ctx, ox::FileAddress const&file) noexcept;
ox::Result<std::size_t> getPreloadAddr(keel::Context &ctx, ox::CRStringView file) noexcept;
void createUuidMapping(Context &ctx, ox::StringView filePath, ox::UUID const&uuid) noexcept;
ox::Error buildUuidMap(Context &ctx) noexcept;
ox::Result<ox::UUID> pathToUuid(Context &ctx, ox::CRStringView path) noexcept;
ox::Result<ox::UUID> getUuid(Context &ctx, ox::FileAddress const&fileAddr) noexcept;
ox::Result<ox::UUID> getUuid(Context &ctx, ox::StringView path) noexcept;
ox::Result<ox::CStringView> getPath(Context &ctx, ox::FileAddress const&fileAddr) noexcept;
ox::Result<ox::CStringView> getPath(Context &ctx, ox::CStringView fileId) noexcept;
constexpr ox::Result<ox::UUID> uuidUrlToUuid(ox::StringView uuidUrl) noexcept {
return ox::UUID::fromString(substr(uuidUrl, 7));
}
ox::Result<ox::CStringView> uuidUrlToPath(Context &ctx, ox::StringView uuid) noexcept;
ox::Result<ox::CStringView> uuidToPath(Context &ctx, ox::CRStringView uuid) noexcept;
ox::Result<ox::CStringView> uuidToPath(Context &ctx, ox::UUID const&uuid) noexcept;
ox::Error performPackTransforms(Context &ctx, ox::Buffer &clawData) noexcept;
#ifndef OX_BARE_METAL
namespace detail {
template<typename T>
ox::Result<keel::AssetRef<T>> readObjFile(
keel::Context &ctx,
ox::StringView assetId,
bool forceLoad) noexcept {
constexpr auto readConvert = [](Context &ctx, const ox::Buffer &buff) -> ox::Result<T> {
constexpr auto makeLoader(Context &ctx) {
return [&ctx](ox::StringView assetId) -> ox::Result<T> {
ox::StringView path;
oxRequire(p, ctx.uuidToPath.at(assetId));
path = *p;
oxRequire(buff, ctx.rom->read(path));
auto [obj, err] = readAsset<T>(buff);
if (err) {
if (err != ox::Error_ClawTypeVersionMismatch && err != ox::Error_ClawTypeMismatch) {
@ -49,25 +78,34 @@ ox::Result<keel::AssetRef<T>> readObjFile(
}
return std::move(obj);
};
ox::StringView path;
};
}
template<typename T>
ox::Result<keel::AssetRef<T>> readObjFile(
keel::Context &ctx,
ox::StringView assetId,
bool forceLoad) noexcept {
ox::UUIDStr uuidStr;
if (beginsWith(assetId, "uuid://")) {
assetId = substr(assetId, 7);
oxRequire(p, ctx.uuidToPath.at(assetId));
path = *p;
oxRequire(p, keel::uuidToPath(ctx, assetId));
} else {
path = assetId;
auto const [uuid, uuidErr] = getUuid(ctx, assetId);
if (!uuidErr) {
uuidStr = uuid.toString();
assetId = uuidStr;
}
}
if (forceLoad) {
oxRequire(buff, ctx.rom->read(path));
oxRequire(obj, readConvert(ctx, buff));
oxRequire(cached, ctx.assetManager.setAsset(assetId, obj));
ctx.assetManager.initTypeManager<T>(detail::makeLoader<T>, ctx);
oxRequire(cached, ctx.assetManager.loadAsset<T>(assetId));
return cached;
} else {
auto [cached, err] = ctx.assetManager.getAsset<T>(assetId);
if (err) {
oxRequire(buff, ctx.rom->read(path));
oxRequire(obj, readConvert(ctx, buff));
oxReturnError(ctx.assetManager.setAsset(assetId, obj).moveTo(cached));
ctx.assetManager.initTypeManager<T>(detail::makeLoader<T>, ctx);
oxReturnError(ctx.assetManager.loadAsset<T>(assetId).moveTo(cached));
}
return cached;
}
@ -89,34 +127,7 @@ ox::Result<keel::AssetRef<T>> readObjNoCache(
#endif
void createUuidMapping(Context &ctx, ox::StringView filePath, ox::UUID const&uuid) noexcept;
ox::Error buildUuidMap(Context &ctx) noexcept;
ox::Result<ox::String> uuidToPath(Context &ctx, ox::CRStringView uuid) noexcept;
ox::Result<ox::String> uuidToPath(Context &ctx, ox::UUID const&uuid) noexcept;
ox::Error performPackTransforms(Context &ctx, ox::Buffer &clawData) noexcept;
template<typename T>
ox::Result<AssetRef<T>> setAsset(keel::Context &ctx, ox::StringView assetId, T const&asset) noexcept {
#ifndef OX_BARE_METAL
if (assetId.len() == 0) {
return OxError(1, "Invalid asset ID");
}
ox::UUIDStr idStr;
if (assetId[0] == '/') {
auto const [id, err] = ctx.pathToUuid.at(assetId);
oxReturnError(err);
idStr = id->toString();
assetId = idStr;
}
return ctx.assetManager.setAsset(assetId, asset);
#else
return OxError(1, "Not supported on this platform");
#endif
}
ox::Error reloadAsset(keel::Context &ctx, ox::StringView assetId) noexcept;
template<typename T>
ox::Result<keel::AssetRef<T>> readObj(

View File

@ -86,19 +86,21 @@ ox::Error preloadObj(
ox::TypeStore &ts,
ox::FileSystem &romFs,
ox::Preloader<PlatSpec> &pl,
ox::CRStringView path) noexcept {
ox::StringView const path) noexcept {
// load file
oxRequireM(buff, romFs.read(path));
oxRequireM(obj, keel::readAsset(ts, buff));
if (obj.type()->preloadable) {
oxOutf("preloading {} as a {}\n", path, obj.type()->typeName);
// preload
oxRequire(a, pl.startAlloc(ox::sizeOf<GbaPlatSpec>(&obj), ox::alignOf<GbaPlatSpec>(obj)));
auto const size = ox::sizeOf<GbaPlatSpec>(&obj);
auto const alignment = ox::alignOf<GbaPlatSpec>(obj);
oxRequire(a, pl.startAlloc(size, alignment));
auto const err = ox::preload<GbaPlatSpec, ox::ModelObject>(&pl, &obj);
oxReturnError(pl.endAlloc());
oxReturnError(err);
keel::PreloadPtr const p{.preloadAddr = static_cast<uint32_t>(a)};
keel::PreloadPtr const p{.preloadAddr = a};
oxReturnError(ox::writeMC(p).moveTo(buff));
oxOutf("preloaded {} as a {} @ {} to {}\n", path, obj.type()->typeName, a, a + size);
} else {
// strip the Claw header (it is not needed after preloading) and write back out to dest fs
oxReturnError(ox::writeMC(obj).moveTo(buff));

View File

@ -83,19 +83,72 @@ ox::Result<ox::UUID> pathToUuid(Context &ctx, ox::CRStringView path) noexcept {
#endif
}
ox::Result<ox::String> uuidToPath(Context &ctx, ox::CRStringView uuid) noexcept {
ox::Result<ox::UUID> getUuid(Context &ctx, ox::FileAddress const&fileAddr) noexcept {
oxRequire(path, fileAddr.getPath());
return getUuid(ctx, path);
}
ox::Result<ox::UUID> getUuid(Context &ctx, ox::StringView path) noexcept {
if (beginsWith(path, "uuid://")) {
auto const uuid = substr(path, 7);
return ox::UUID::fromString(uuid);
} else {
return pathToUuid(ctx, path);
}
}
ox::Result<ox::CStringView> getPath(Context &ctx, ox::FileAddress const&fileAddr) noexcept {
oxRequire(path, fileAddr.getPath());
if (beginsWith(path, "uuid://")) {
auto const uuid = substr(path, 7);
#ifndef OX_BARE_METAL
oxRequire(out, ctx.uuidToPath.at(uuid));
return *out;
oxRequireM(out, ctx.uuidToPath.at(uuid));
return ox::CStringView{*out};
#else
return OxError(1, "UUID to path conversion not supported on this platform");
#endif
} else {
return ox::CStringView{path};
}
}
ox::Result<ox::CStringView> getPath(Context &ctx, ox::CStringView fileId) noexcept {
if (beginsWith(fileId, "uuid://")) {
auto const uuid = substr(fileId, 7);
#ifndef OX_BARE_METAL
oxRequireM(out, ctx.uuidToPath.at(uuid));
return ox::CStringView{*out};
#else
return OxError(1, "UUID to path conversion not supported on this platform");
#endif
} else {
return ox::CStringView{fileId};
}
}
ox::Result<ox::CStringView> uuidUrlToPath(Context &ctx, ox::StringView uuid) noexcept {
uuid = substr(uuid, 7);
#ifndef OX_BARE_METAL
oxRequireM(out, ctx.uuidToPath.at(uuid));
return ox::CStringView(*out);
#else
return OxError(1, "UUID to path conversion not supported on this platform");
#endif
}
ox::Result<ox::String> uuidToPath(Context &ctx, ox::UUID const&uuid) noexcept {
ox::Result<ox::CStringView> uuidToPath(Context &ctx, ox::CRStringView uuid) noexcept {
#ifndef OX_BARE_METAL
oxRequire(out, ctx.uuidToPath.at(uuid.toString()));
return *out;
oxRequireM(out, ctx.uuidToPath.at(uuid));
return ox::CStringView(*out);
#else
return OxError(1, "UUID to path conversion not supported on this platform");
#endif
}
ox::Result<ox::CStringView> uuidToPath(Context &ctx, ox::UUID const&uuid) noexcept {
#ifndef OX_BARE_METAL
oxRequireM(out, ctx.uuidToPath.at(uuid.toString()));
return ox::CStringView(*out);
#else
return OxError(1, "UUID to path conversion not supported on this platform");
#endif
@ -113,6 +166,21 @@ ox::Error performPackTransforms(Context &ctx, ox::Buffer &clawData) noexcept {
return {};
}
ox::Error reloadAsset(keel::Context &ctx, ox::StringView assetId) noexcept {
ox::UUIDStr uuidStr;
if (beginsWith(assetId, "uuid://")) {
assetId = substr(assetId, 7);
oxRequire(p, keel::uuidToPath(ctx, assetId));
} else {
auto const [uuid, uuidErr] = getUuid(ctx, assetId);
if (!uuidErr) {
uuidStr = uuid.toString();
assetId = uuidStr;
}
}
return ctx.assetManager.reloadAsset(assetId);
}
}
#else
@ -166,6 +234,10 @@ ox::Result<std::size_t> getPreloadAddr(keel::Context &ctx, ox::FileAddress const
return static_cast<std::size_t>(p.preloadAddr) + ctx.preloadSectionOffset;
}
ox::Error reloadAsset(keel::Context&, ox::StringView) noexcept {
return OxError(1, "reloadAsset is unsupported on this platform");
}
}
#endif

View File

@ -11,7 +11,7 @@
#include <keel/keel.hpp>
static ox::Error writeFileBuff(ox::StringView path, ox::Buffer const&buff) noexcept {
static ox::Error writeFileBuff(ox::StringView path, ox::BufferView const buff) noexcept {
try {
std::ofstream f(std::string(toStdStringView(path)), std::ios::binary);
f.write(buff.data(), static_cast<intptr_t>(buff.size()));
@ -39,10 +39,10 @@ static ox::Result<ox::Buffer> readFileBuff(ox::StringView path) noexcept {
}
}
static ox::Error generateTypes(ox::TypeStore *ts) noexcept {
static ox::Error generateTypes(ox::TypeStore &ts) noexcept {
for (auto const mod : keel::modules()) {
for (auto gen : mod->types()) {
oxReturnError(gen(*ts));
oxReturnError(gen(ts));
}
}
return {};
@ -54,7 +54,7 @@ static ox::Error pack(ox::StringView argSrc, ox::StringView argRomBin, ox::Strin
ox::FileSystem32 dst(dstBuff);
oxRequire(ctx, keel::init(ox::make_unique<ox::PassThroughFS>(argSrc), "keel-pack"));
keel::TypeStore ts(*ctx->rom, ox::sfmt("{}/type_descriptors", projectDataDir));
oxReturnError(generateTypes(&ts));
oxReturnError(generateTypes(ts));
oxReturnError(keel::pack(*ctx, ts, dst));
oxRequireM(pl, keel::GbaPreloader::make());
oxReturnError(preload(ts, dst, *pl));
@ -62,14 +62,13 @@ static ox::Error pack(ox::StringView argSrc, ox::StringView argRomBin, ox::Strin
// resize buffer
oxRequire(dstSize, dst.size());
dstBuff.resize(dstSize);
// concatenate ROM segments
oxRequireM(romBuff, readFileBuff(argRomBin));
oxReturnError(appendBinary(romBuff, dstBuff, *pl));
oxOutf("Input exe size: {} bytes\n", romBuff.size());
oxOutf("Dest FS size: {} bytes\n", dstSize);
oxOutf("Preload buff size: {} bytes\n", pl->buff().size());
oxOutf("ROM buff size: {} bytes\n", romBuff.size());
oxReturnError(appendBinary(romBuff, dstBuff, *pl));
oxOutf("Final ROM buff size: {} bytes\n", romBuff.size());
oxReturnError(writeFileBuff(argRomBin, romBuff));
return {};
}

View File

@ -30,11 +30,11 @@ static ox::Error pathToInode(
}
if (beginsWith(path, "uuid://")) {
auto const uuid = ox::substr(path, 7);
oxReturnError(keel::uuidToPath(ctx, uuid).moveTo(path));
oxReturnError(keel::uuidToPath(ctx, uuid).to<ox::String>().moveTo(path));
}
oxRequire(s, dest.stat(path));
oxReturnError(o.at("type").unwrap()->set(static_cast<int8_t>(ox::FileAddressType::Inode)));
oxOutf("path to inode: {} => {}\n", path, s.inode);
oxOutf("\tpath to inode: {} => {}\n", path, s.inode);
return data.set(2, s.inode);
}
@ -97,6 +97,7 @@ static ox::Error doTransformations(
oxReturnError(keel::performPackTransforms(ctx, buff));
// transform FileAddresses
oxRequireM(obj, keel::readAsset(ts, buff));
oxOutf("transforming {}\n", filePath);
oxReturnError(transformFileAddressesObj(ctx, dest, obj));
oxReturnError(ox::writeClaw(obj).moveTo(buff));
// write file to dest
@ -143,16 +144,15 @@ static ox::Error copy(
if (beginsWith(name, ".")) {
continue;
}
oxOutf("reading {}\n", currentFile);
oxRequire(stat, src.stat(currentFile));
if (stat.fileType == ox::FileType::Directory) {
oxReturnError(dest.mkdir(currentFile, true));
oxReturnError(copy(src, dest, currentFile + '/'));
} else {
// load file
oxOutf("copying file: {}\n", currentFile);
oxRequireM(buff, src.read(currentFile));
// write file to dest
oxOutf("writing {}\n", currentFile);
oxReturnError(dest.write(currentFile, buff));
}
}

View File

@ -2,6 +2,7 @@
* Copyright 2016 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <algorithm>
#include <filesystem>
#include <imgui.h>
@ -196,10 +197,10 @@ void StudioUI::drawMenu() noexcept {
if (ImGui::BeginMenu("Edit")) {
auto undoStack = m_activeEditor ? m_activeEditor->undoStack() : nullptr;
if (ImGui::MenuItem("Undo", "Ctrl+Z", false, undoStack && undoStack->canUndo())) {
undoStack->undo();
oxLogError(undoStack->undo());
}
if (ImGui::MenuItem("Redo", "Ctrl+Y", false, undoStack && undoStack->canRedo())) {
undoStack->redo();
oxLogError(undoStack->redo());
}
ImGui::Separator();
if (ImGui::MenuItem("Copy", "Ctrl+C", false, m_activeEditor && m_activeEditor->copyEnabled())) {
@ -208,7 +209,7 @@ void StudioUI::drawMenu() noexcept {
if (ImGui::MenuItem("Cut", "Ctrl+X", false, m_activeEditor && m_activeEditor->cutEnabled())) {
m_activeEditor->cut();
}
if (ImGui::MenuItem("Paste", "Ctrl+V", false, m_activeEditor && m_activeEditor->pasteEnabled()) && m_activeEditor) {
if (ImGui::MenuItem("Paste", "Ctrl+V", false, m_activeEditor && m_activeEditor->pasteEnabled())) {
m_activeEditor->paste();
}
ImGui::EndMenu();
@ -314,14 +315,14 @@ void StudioUI::toggleProjectExplorer() noexcept {
void StudioUI::redo() noexcept {
auto undoStack = m_activeEditor ? m_activeEditor->undoStack() : nullptr;
if (undoStack && undoStack->canRedo()) {
m_activeEditor->undoStack()->redo();
oxLogError(m_activeEditor->undoStack()->redo());
}
}
void StudioUI::undo() noexcept {
auto undoStack = m_activeEditor ? m_activeEditor->undoStack() : nullptr;
if (undoStack && undoStack->canUndo()) {
m_activeEditor->undoStack()->undo();
oxLogError(m_activeEditor->undoStack()->undo());
}
}

View File

@ -22,4 +22,9 @@ struct StudioContext {
ui(pUi), tctx(pTctx) {}
};
[[nodiscard]]
inline keel::Context &keelCtx(StudioContext &ctx) noexcept {
return keelCtx(ctx.tctx);
}
}

View File

@ -43,6 +43,9 @@ class BaseEditor: public Widget {
virtual void paste();
[[nodiscard]]
virtual bool acceptsClipboardPayload() const noexcept;
virtual void exportFile();
virtual void keyStateChanged(turbine::Key key, bool down);
@ -131,14 +134,14 @@ class Editor: public studio::BaseEditor {
[[nodiscard]]
UndoStack *undoStack() noexcept final;
void pushCommand(ox::UPtr<UndoCommand> &&cmd) noexcept;
ox::Error pushCommand(ox::UPtr<UndoCommand> &&cmd) noexcept;
template<typename UC, typename ...Args>
void pushCommand(Args&&... args) noexcept {
ox::Error pushCommand(Args&&... args) noexcept {
try {
m_undoStack.push(ox::make_unique<UC>(ox::forward<Args>(args)...));
return m_undoStack.push(ox::make_unique<UC>(ox::forward<Args>(args)...));
} catch (ox::Exception const&ex) {
oxLogError(ex.toError());
return ex.toError();
}
}

View File

@ -34,7 +34,7 @@ ox::Result<T> getDragDropPayload(ox::CStringView name) noexcept {
static_cast<size_t>(payload->DataSize)});
}
ox::Error setDragDropPayload(ox::CStringView name, auto const &obj) noexcept {
ox::Error setDragDropPayload(ox::CStringView name, auto const&obj) noexcept {
oxRequire(buff, ox::writeClaw(obj, ox::ClawFormat::Metal));
ImGui::SetDragDropPayload(name.c_str(), buff.data(), buff.size());
return {};
@ -148,6 +148,19 @@ bool BeginPopup(turbine::Context &ctx, ox::CStringView popupName, bool &show, Im
*/
bool ComboBox(ox::CStringView lbl, ox::SpanView<ox::String> list, size_t &selectedIdx) noexcept;
/**
*
* @param lbl
* @param callback
* @param selectedIdx
* @return true if new value selected, false otherwise
*/
bool ComboBox(
ox::CStringView lbl,
std::function<ox::CStringView(size_t)> const&f,
size_t strCnt,
size_t &selectedIdx) noexcept;
bool FileComboBox(
ox::CStringView lbl,
studio::StudioContext &sctx,

View File

@ -31,7 +31,7 @@ enum class ProjectEvent {
constexpr ox::Result<ox::StringView> fileExt(ox::CRStringView path) noexcept {
auto const extStart = ox::find(path.crbegin(), path.crend(), '.').offset();
if (!extStart) {
return OxError(1, "Cannot open a file without valid extension.");
return OxError(1, "file path does not have valid extension");
}
return substr(path, extStart + 1);
}
@ -47,6 +47,7 @@ constexpr ox::StringView parentDir(ox::StringView path) noexcept {
class Project {
private:
ox::SmallMap<ox::String, ox::Optional<ox::ClawFormat>> m_typeFmt;
keel::Context &m_ctx;
ox::String m_path;
ox::String m_projectDataDir;
@ -75,7 +76,15 @@ class Project {
ox::Error writeObj(
ox::CRStringView path,
T const&obj,
ox::ClawFormat fmt = ox::ClawFormat::Metal) noexcept;
ox::ClawFormat fmt) noexcept;
/**
* Writes a MetalClaw object to the project at the given path.
*/
template<typename T>
ox::Error writeObj(
ox::CRStringView path,
T const&obj) noexcept;
template<typename T>
ox::Result<T> loadObj(ox::CRStringView path) const noexcept;
@ -115,7 +124,7 @@ class Project {
// file.
ox::Signal<ox::Error(ox::CRStringView)> fileRecognized;
ox::Signal<ox::Error(ox::CRStringView)> fileDeleted;
ox::Signal<ox::Error(ox::CRStringView)> fileUpdated;
ox::Signal<ox::Error(ox::StringView, ox::UUID)> fileUpdated;
};
@ -130,15 +139,24 @@ ox::Error Project::writeObj(ox::CRStringView path, T const&obj, ox::ClawFormat f
oxReturnError(ox::buildTypeDef(&m_typeStore, &obj));
}
oxRequire(desc, m_typeStore.get<T>());
auto const descExists = m_fs.stat(ox::sfmt("{}/{}", m_typeDescPath, buildTypeId(*desc))).error != 0;
auto const descPath = ox::sfmt("{}/{}", m_typeDescPath, buildTypeId(*desc));
auto const descExists = m_fs.exists(descPath);
if (!descExists) {
oxReturnError(writeTypeStore());
}
oxReturnError(keel::setAsset(m_ctx, path, obj));
fileUpdated.emit(path);
oxReturnError(keel::reloadAsset(m_ctx, path));
oxRequire(uuid, pathToUuid(m_ctx, path));
fileUpdated.emit(path, uuid);
return {};
}
template<typename T>
ox::Error Project::writeObj(ox::CRStringView path, T const&obj) noexcept {
oxRequire(ext, fileExt(path));
auto const fmt = m_typeFmt[ext].or_value(ox::ClawFormat::Metal);
return writeObj(path, obj, fmt);
}
template<typename T>
ox::Result<T> Project::loadObj(ox::CRStringView path) const noexcept {
oxRequire(buff, loadBuff(path));

View File

@ -0,0 +1,133 @@
#pragma once
#include <imgui.h>
#include <ox/std/math.hpp>
#include <ox/std/point.hpp>
#include <ox/std/size.hpp>
#include <ox/std/typetraits.hpp>
namespace studio {
struct Selection {
ox::Point a, b;
constexpr Selection() noexcept = default;
constexpr Selection(ox::Point const&pA, ox::Point const&pB) noexcept: a(pA), b(pB) {}
[[nodiscard]]
constexpr ox::Size size() const noexcept {
return {b.x - a.x, b.y - a.y};
}
[[nodiscard]]
constexpr bool contains(ox::Point const&pt) const noexcept {
return a.x <= pt.x && a.y <= pt.y
&& b.x >= pt.x && b.y >= pt.y;
}
};
constexpr auto iterateSelection(studio::Selection const&sel, auto const&cb) {
constexpr auto retErr = ox::is_same_v<decltype(cb(0, 0)), ox::Error>;
for (auto x = sel.a.x; x <= sel.b.x; ++x) {
for (auto y = sel.a.y; y <= sel.b.y; ++y) {
if constexpr(retErr) {
oxReturnError(cb(x, y));
} else {
cb(x, y);
}
}
}
if constexpr(retErr) {
return ox::Error{};
}
};
constexpr auto iterateSelectionRows(studio::Selection const&sel, auto const&cb) {
constexpr auto retErr = ox::is_same_v<decltype(cb(0, 0)), ox::Error>;
for (auto y = sel.a.y; y <= sel.b.y; ++y) {
for (auto x = sel.a.x; x <= sel.b.x; ++x) {
if constexpr(retErr) {
oxReturnError(cb(x, y));
} else {
cb(x, y);
}
}
}
if constexpr(retErr) {
return ox::Error{};
}
};
class SelectionTracker {
private:
bool m_selectionOngoing{};
ox::Point m_pointA;
ox::Point m_pointB;
public:
[[nodiscard]]
constexpr bool selectionOngoing() const noexcept {
return m_selectionOngoing;
}
constexpr void startSelection(ox::Point cursor) noexcept {
m_pointA = cursor;
m_pointB = cursor;
m_selectionOngoing = true;
}
/**
*
* @param cursor
* @param allowStart
* @return true if changed, false otherwise
*/
constexpr bool updateCursorPoint(ox::Point cursor, bool allowStart = true) noexcept {
auto changed = false;
if (!m_selectionOngoing && allowStart) {
m_pointA = cursor;
m_selectionOngoing = true;
}
if (m_selectionOngoing) {
m_pointB = cursor;
changed = true;
}
return changed;
}
constexpr void updateCursorPoint(ox::Vec2 cursor, bool allowStart = true) noexcept {
updateCursorPoint(
ox::Point{
static_cast<int32_t>(cursor.x),
static_cast<int32_t>(cursor.y),
},
allowStart);
}
constexpr void updateCursorPoint(ImVec2 cursor, bool allowStart = true) noexcept {
updateCursorPoint(
ox::Point{
static_cast<int32_t>(cursor.x),
static_cast<int32_t>(cursor.y),
},
allowStart);
}
constexpr void finishSelection() noexcept {
m_selectionOngoing = {};
}
[[nodiscard]]
constexpr Selection selection() const noexcept {
return {
{
ox::min(m_pointA.x, m_pointB.x),
ox::min(m_pointA.y, m_pointB.y),
},
{
ox::max(m_pointA.x, m_pointB.x),
ox::max(m_pointA.y, m_pointB.y),
},
};
}
};
}

View File

@ -4,6 +4,7 @@
#pragma once
#include <studio/configio.hpp>
#include <studio/context.hpp>
#include <studio/editor.hpp>
#include <studio/filedialog.hpp>
@ -12,6 +13,7 @@
#include <studio/itemmaker.hpp>
#include <studio/popup.hpp>
#include <studio/project.hpp>
#include <studio/selectiontracker.hpp>
#include <studio/task.hpp>
#include <studio/undocommand.hpp>
#include <studio/undostack.hpp>

View File

@ -4,16 +4,26 @@
#pragma once
#include <source_location>
#include <ox/std/error.hpp>
namespace studio {
class NoChangesException: public ox::Exception {
public:
inline NoChangesException(std::source_location sloc = std::source_location::current()):
ox::Exception(sloc.file_name(), sloc.line(), 1, "Command makes no changes.") {}
};
class UndoCommand {
public:
virtual ~UndoCommand() noexcept = default;
virtual void redo() noexcept = 0;
virtual void undo() noexcept = 0;
virtual ox::Error redo() noexcept = 0;
virtual ox::Error undo() noexcept = 0;
[[nodiscard]]
virtual int commandId() const noexcept = 0;
virtual bool mergeWith(UndoCommand const*cmd) noexcept;
virtual bool mergeWith(UndoCommand const&cmd) noexcept;
};
}

View File

@ -19,11 +19,11 @@ class UndoStack {
std::size_t m_stackIdx = 0;
public:
void push(ox::UPtr<UndoCommand> &&cmd) noexcept;
ox::Error push(ox::UPtr<UndoCommand> &&cmd) noexcept;
void redo() noexcept;
ox::Error redo() noexcept;
void undo() noexcept;
ox::Error undo() noexcept;
[[nodiscard]]
constexpr bool canRedo() const noexcept {

View File

@ -23,6 +23,10 @@ void BaseEditor::copy() {
void BaseEditor::paste() {
}
bool BaseEditor::acceptsClipboardPayload() const noexcept {
return {};
}
void BaseEditor::exportFile() {
}
@ -95,7 +99,7 @@ void BaseEditor::setPasteEnabled(bool v) {
}
bool BaseEditor::pasteEnabled() const noexcept {
return m_pasteEnabled;
return m_pasteEnabled && acceptsClipboardPayload();
}
ox::Error BaseEditor::saveItem() noexcept {
@ -127,8 +131,8 @@ ox::CStringView Editor::itemDisplayName() const noexcept {
return m_itemName;
}
void Editor::pushCommand(ox::UPtr<UndoCommand> &&cmd) noexcept {
m_undoStack.push(std::move(cmd));
ox::Error Editor::pushCommand(ox::UPtr<UndoCommand> &&cmd) noexcept {
return m_undoStack.push(std::move(cmd));
}
UndoStack *Editor::undoStack() noexcept {

View File

@ -100,6 +100,26 @@ bool ComboBox(
return out;
}
bool ComboBox(
ox::CStringView lbl,
std::function<ox::CStringView(size_t)> const&f,
size_t strCnt,
size_t &selectedIdx) noexcept {
bool out{};
auto const first = selectedIdx < strCnt ? f(selectedIdx).c_str() : "";
if (ImGui::BeginCombo(lbl.c_str(), first, 0)) {
for (auto i = 0u; i < strCnt; ++i) {
const auto selected = (selectedIdx == i);
if (ImGui::Selectable(f(i).c_str(), selected) && selectedIdx != i) {
selectedIdx = i;
out = true;
}
}
ImGui::EndCombo();
}
return out;
}
bool FileComboBox(
ox::CStringView lbl,
studio::StudioContext &sctx,

View File

@ -56,9 +56,13 @@ ox::FileSystem &Project::romFs() noexcept {
}
ox::Error Project::mkdir(ox::CRStringView path) const noexcept {
oxReturnError(m_fs.mkdir(path, true));
fileUpdated.emit(path);
return {};
auto const [stat, err] = m_fs.stat(path);
if (err) {
oxReturnError(m_fs.mkdir(path, true));
fileUpdated.emit(path, {});
}
return stat.fileType == ox::FileType::Directory ?
ox::Error{} : OxError(1, "path exists as normal file");
}
ox::Result<ox::FileStat> Project::stat(ox::CRStringView path) const noexcept {
@ -115,9 +119,9 @@ ox::Error Project::writeBuff(ox::CRStringView path, ox::Buffer const&buff) noexc
ox::Buffer outBuff;
outBuff.reserve(buff.size() + HdrSz);
ox::BufferWriter writer(&outBuff);
auto const [uuid, err] = m_ctx.pathToUuid.at(path);
auto const [uuid, err] = pathToUuid(m_ctx, path);
if (!err) {
oxReturnError(keel::writeUuidHeader(writer, *uuid));
oxReturnError(keel::writeUuidHeader(writer, uuid));
}
oxReturnError(writer.write(buff.data(), buff.size()));
auto const newFile = m_fs.stat(path).error != 0;
@ -126,7 +130,7 @@ ox::Error Project::writeBuff(ox::CRStringView path, ox::Buffer const&buff) noexc
fileAdded.emit(path);
indexFile(path);
} else {
fileUpdated.emit(path);
fileUpdated.emit(path, uuid);
}
return {};
}

View File

@ -3,7 +3,7 @@
namespace studio {
bool UndoCommand::mergeWith(UndoCommand const*) noexcept {
bool UndoCommand::mergeWith(UndoCommand const&) noexcept {
return false;
}

View File

@ -6,35 +6,39 @@
namespace studio {
void UndoStack::push(ox::UPtr<UndoCommand> &&cmd) noexcept {
ox::Error UndoStack::push(ox::UPtr<UndoCommand> &&cmd) noexcept {
for (auto const i = m_stackIdx; i < m_stack.size();) {
std::ignore = m_stack.erase(i);
}
cmd->redo();
oxReturnError(cmd->redo());
redoTriggered.emit(cmd.get());
changeTriggered.emit(cmd.get());
if (m_stack.empty() || !(*m_stack.back().value)->mergeWith(cmd.get())) {
if (m_stack.empty() || !(*m_stack.back().value)->mergeWith(*cmd)) {
m_stack.emplace_back(std::move(cmd));
++m_stackIdx;
}
return {};
}
void UndoStack::redo() noexcept {
ox::Error UndoStack::redo() noexcept {
if (m_stackIdx < m_stack.size()) {
auto &c = m_stack[m_stackIdx++];
c->redo();
auto &c = m_stack[m_stackIdx];
oxReturnError(c->redo());
++m_stackIdx;
redoTriggered.emit(c.get());
changeTriggered.emit(c.get());
}
return {};
}
void UndoStack::undo() noexcept {
ox::Error UndoStack::undo() noexcept {
if (m_stackIdx) {
auto &c = m_stack[--m_stackIdx];
c->undo();
oxReturnError(c->undo());
undoTriggered.emit(c.get());
changeTriggered.emit(c.get());
}
return {};
}
}

View File

@ -17,19 +17,14 @@ class BaseClipboardObject {
virtual ~BaseClipboardObject() noexcept = default;
[[nodiscard]]
virtual ox::String typeId() const noexcept = 0;
[[nodiscard]]
constexpr auto typeMatch(ox::StringView name, int version) const noexcept {
return typeId() == ox::buildTypeId(name, version);
}
virtual ox::StringView typeId() const noexcept = 0;
};
template<typename T>
class ClipboardObject: public BaseClipboardObject {
[[nodiscard]]
ox::String typeId() const noexcept final {
return ox::buildTypeId(T::TypeName, T::TypeVersion);
ox::StringView typeId() const noexcept final {
return ox::ModelTypeId_v<T>;
}
};
@ -39,11 +34,11 @@ void setClipboardText(Context &ctx, ox::CRStringView text) noexcept;
void setClipboardObject(Context &ctx, ox::UPtr<BaseClipboardObject> &&obj) noexcept;
ox::Result<BaseClipboardObject*> getClipboardData(Context &ctx, ox::StringView typeName, int typeVersion) noexcept;
ox::Result<BaseClipboardObject*> getClipboardData(Context &ctx, ox::StringView typeId) noexcept;
template<typename T>
ox::Result<T*> getClipboardObject(Context &ctx) noexcept {
oxRequire(p, getClipboardData(ctx, T::TypeName, T::TypeVersion));
oxRequire(p, getClipboardData(ctx, ox::ModelTypeId_v<T>));
return dynamic_cast<T*>(p);
}

View File

@ -26,8 +26,8 @@ void setClipboardObject(Context &ctx, ox::UPtr<BaseClipboardObject> &&obj) noexc
ctx.clipboard = std::move(obj);
}
ox::Result<BaseClipboardObject*> getClipboardData(Context &ctx, ox::StringView typeName, int typeVersion) noexcept {
if (ctx.clipboard && ctx.clipboard->typeMatch(typeName, typeVersion)) {
ox::Result<BaseClipboardObject*> getClipboardData(Context &ctx, ox::StringView typeId) noexcept {
if (ctx.clipboard && ctx.clipboard->typeId() == typeId) {
return ctx.clipboard.get();
}
return OxError(1);