Compare commits

...

44 Commits

Author SHA1 Message Date
1057099fcf [nostalgia] Update release notes
All checks were successful
Build / build (push) Successful in 1m17s
2025-06-22 01:01:46 -05:00
17577b6e8b [nostalgia/gfx/studio/tilesheet] Disable paste when nothing is selected 2025-06-22 01:01:44 -05:00
db442e2775 [nostalgia/gfx] Address a couple of implicit conversions
All checks were successful
Build / build (push) Successful in 1m20s
2025-06-21 14:21:23 -05:00
23cd50d29e [studio] Fix Studio to clear editor pointers when opening a new project 2025-06-21 14:21:12 -05:00
28088d1ed1 [nostalgia/studio] Set version to d2025.06.0
All checks were successful
Build / build (push) Successful in 1m16s
2025-06-21 08:55:52 -05:00
baf5fa3199 [nostalgia] Move d2025.05.2 release notes to d2025.06.0
All checks were successful
Build / build (push) Successful in 1m19s
2025-06-21 08:54:57 -05:00
857587c18b [studio] Cleanup
All checks were successful
Build / build (push) Successful in 1m16s
2025-06-21 08:31:27 -05:00
eb3d53c955 [studio] Cleanup
All checks were successful
Build / build (push) Successful in 1m15s
2025-06-21 08:22:13 -05:00
14d58f3f5b [studio] Fix Navigation shortcuts for non-Mac systems
All checks were successful
Build / build (push) Successful in 1m18s
2025-06-21 08:13:14 -05:00
5f2397903a [studio,nostalgia/gfx/studio/tilesheet] Fix copy/cut/paste enablement when there is no selection
All checks were successful
Build / build (push) Successful in 2m2s
2025-06-21 00:20:06 -05:00
58e0ecb469 [studio] Make FilePickerPopup accept on double click of a file
All checks were successful
Build / build (push) Successful in 1m22s
2025-06-20 23:58:29 -05:00
8838bf420e [studio] Fix to properly copy file that has the same name as deleted file
All checks were successful
Build / build (push) Successful in 1m24s
2025-06-20 23:36:20 -05:00
bddc544d7c [nostalgia] Update release notes
All checks were successful
Build / build (push) Successful in 1m33s
2025-06-20 22:48:39 -05:00
a9437191bf [studio,turbine] Add support for mouse back/forward buttons
All checks were successful
Build / build (push) Successful in 1m30s
2025-06-20 22:44:09 -05:00
9d8da7ccda [ox/std] Make strToInt return error for empty string
Some checks failed
Build / build (push) Has been cancelled
2025-06-20 22:43:33 -05:00
394b568e72 [studio] Add Back/Forward navigation
All checks were successful
Build / build (push) Successful in 1m14s
2025-06-20 20:58:42 -05:00
78e9f70db6 [nostalgia] Update release notes
All checks were successful
Build / build (push) Successful in 1m20s
2025-06-19 23:58:42 -05:00
12e5623fe6 [ox/logconn] Add exception handling for logger thread
All checks were successful
Build / build (push) Successful in 1m18s
2025-06-19 23:56:26 -05:00
cfdfb0a8c9 [studio] Fix file deletion to close file even if not active 2025-06-19 23:55:47 -05:00
56e665301f [studio] Cleanup
All checks were successful
Build / build (push) Successful in 1m29s
2025-06-19 21:37:57 -05:00
7415ce4bd9 [nostalgia/gfx/studio] Cleanup
All checks were successful
Build / build (push) Successful in 2m53s
2025-06-17 20:45:24 -05:00
05f42150a1 [olympic] Add new loc command to Makefile
All checks were successful
Build / build (push) Successful in 1m19s
2025-06-09 21:29:27 -05:00
8ea2bc6934 [nostalgia] Update release notes
All checks were successful
Build / build (push) Successful in 1m16s
2025-06-09 00:22:17 -05:00
c780924122 [studio] Add [DEBUG] tag to About in debug builds
All checks were successful
Build / build (push) Successful in 1m18s
2025-06-08 21:33:17 -05:00
8c538560ca [nostalgia/gfx/studio/palette] Make RGB key shortcuts work when color channel inputs are focused
All checks were successful
Build / build (push) Successful in 1m16s
2025-06-08 17:00:52 -05:00
c3e75bdb55 [nostalgia/gfx/studio/tilesheet] Cleanup
All checks were successful
Build / build (push) Successful in 1m16s
2025-06-08 16:26:55 -05:00
e78c405046 [nostalgia/gfx] Cleanup
All checks were successful
Build / build (push) Successful in 1m19s
2025-05-31 01:53:59 -05:00
6b7c5d896e [nostalgia] Update release notes
All checks were successful
Build / build (push) Successful in 1m14s
2025-05-31 01:48:07 -05:00
a33a73d73a [nostalgia/gfx/studio/palette] Add preview to color editor
All checks were successful
Build / build (push) Successful in 1m16s
2025-05-31 01:34:00 -05:00
e1cfcc8b5f [nostalgia] Update release notes
All checks were successful
Build / build (push) Successful in 1m13s
2025-05-30 23:37:58 -05:00
514cb97835 [nostalgia/gfx/studio/tilesheet] Fix draw command to work on same pixel after switching subsheets 2025-05-30 23:32:51 -05:00
4b5218c4f1 [nostalgia/gfx] Cleanup 2025-05-30 23:32:27 -05:00
2ca77173d3 [keel] Add isUuidUrl function 2025-05-30 23:29:42 -05:00
cce5f52f96 [nostalgia/gfx/studio/tilesheet] Fix manual redo of draw actions, fix drawing to pixel 0, 0 as first action
All checks were successful
Build / build (push) Successful in 1m15s
2025-05-29 22:12:10 -05:00
b55993294a [studio/modlib] Fix headerizeConfigFile to handle slashes in file name
All checks were successful
Build / build (push) Successful in 1m12s
2025-05-25 01:20:20 -05:00
d8f847d707 [studio/applib] Move popup types into their own directory
All checks were successful
Build / build (push) Successful in 1m20s
2025-05-24 15:01:17 -05:00
8419b137e5 [turbine,studio] Fix some popup window resize weirdness, cleanup some function names
All checks were successful
Build / build (push) Successful in 1m17s
2025-05-24 01:32:14 -05:00
ed1160ec74 [nostalgia] Update release notes
All checks were successful
Build / build (push) Successful in 1m14s
2025-05-24 00:50:22 -05:00
1e21778059 [nostalgia/gfx/studio/tilesheet] Ensure config file has a Claw header 2025-05-24 00:48:54 -05:00
78379f58c8 [studio] Add ability to remember recent projects in config 2025-05-24 00:48:10 -05:00
4322f720ba [keel] Fix ox::Result<DstType> convert(Context &ctx, ox::BufferView const&src) 2025-05-24 00:47:39 -05:00
26f1a6051f [ox/std] Make Vector::remove take a MaybeView_t
All checks were successful
Build / build (push) Successful in 1m17s
2025-05-24 00:46:39 -05:00
c4c1d4777b [keel] Cleanup ox::Error(0) instance 2025-05-23 20:25:08 -05:00
fab012d3e7 [ox] Cleanup all ox::Error(0) instances 2025-05-23 20:24:58 -05:00
90 changed files with 1007 additions and 485 deletions

View File

@@ -52,3 +52,25 @@ configure-gba:
.PHONY: configure-gba-debug
configure-gba-debug:
${BC_CMD_SETUP_BUILD} --toolchain=deps/gbabuildcore/cmake/modules/GBA.cmake --target=gba --current_build=0 --build_type=debug --build_root=${BC_VAR_BUILD_PATH}
.PHONY: loc
loc:
${BC_PY3} util/scripts/loc.py \
--search-dirs \
src \
deps/ox/src \
deps/buildcore \
deps/gbabuildcore \
deps/glutils \
deps/teagba \
--include-exts \
.cpp \
.hpp \
.py \
.s \
.cmake \
--exclude-paths \
deps/teagba/src/gba_crt0.s \
src/olympic/studio/applib/src/font.cpp \
src/olympic/studio/applib/src/font.hpp \
src/nostalgia/studio/icondata.cpp

View File

@@ -39,7 +39,7 @@ struct TypeInfoCatcher {
}
constexpr Error field(...) noexcept {
return ox::Error(0);
return {};
}
static constexpr auto opType() {

View File

@@ -218,7 +218,7 @@ Error Signal<Args...>::emitCheckError(Args... args) const noexcept {
for (auto &f : m_slots) {
f->call(args...);
}
return ox::Error(0);
return {};
} catch (const ox::Exception &ex) {
return ox::Error(ex.errCode, ex.msg, ex.src);
}
@@ -410,7 +410,7 @@ Error Signal<Error(Args...)>::emitCheckError(Args... args) const noexcept {
for (auto &f : m_slots) {
OX_RETURN_ERROR(f->call(ox::forward<Args>(args)...));
}
return ox::Error(0);
return {};
}
}

View File

@@ -230,7 +230,7 @@ template<typename size_t>
Error FileStoreTemplate<size_t>::incLinks(uint64_t id) {
OX_REQUIRE_M(item, find(static_cast<size_t>(id)).validate());
++item->links;
return ox::Error(0);
return {};
}
template<typename size_t>
@@ -240,7 +240,7 @@ Error FileStoreTemplate<size_t>::decLinks(uint64_t id) {
if (item->links == 0) {
OX_RETURN_ERROR(remove(item));
}
return ox::Error(0);
return {};
}
template<typename size_t>
@@ -298,7 +298,7 @@ Error FileStoreTemplate<size_t>::write(uint64_t id64, const void *data, FsSize_t
dest->id.get(), dest.offset(), destData.size());
fsData->rootNode = dest.offset();
oxTracef("ox.fs.FileStoreTemplate.write", "Root inode: {}", dest->id.get());
return ox::Error(0);
return {};
}
} else {
oxTrace("ox.fs.FileStoreTemplate.write.fail", "Could not place item due to absence of FileStore header.");
@@ -427,7 +427,7 @@ Error FileStoreTemplate<size_t>::resize() {
oxTracef("ox.fs.FileStoreTemplate.resize", "resize to: {}", newSize);
OX_RETURN_ERROR(m_buffer->setSize(newSize));
oxTracef("ox.fs.FileStoreTemplate.resize", "resized to: {}", m_buffer->size());
return ox::Error(0);
return {};
}
template<typename size_t>
@@ -479,7 +479,7 @@ Error FileStoreTemplate<size_t>::walk(Error(*cb)(uint8_t, uint64_t, uint64_t)) {
for (auto i = m_buffer->iterator(); i.valid(); i.next()) {
OX_RETURN_ERROR(cb(i->fileType, i.ptr().offset(), i.ptr().end()));
}
return ox::Error(0);
return {};
}
template<typename size_t>
@@ -503,7 +503,7 @@ Error FileStoreTemplate<size_t>::compact() {
return m_buffer->compact([this, &isFirstItem](uint64_t oldAddr, ItemPtr item) -> Error {
if (isFirstItem) {
isFirstItem = false;
return ox::Error(0);
return {};
}
if (!item.valid()) {
return ox::Error(1);
@@ -524,7 +524,7 @@ Error FileStoreTemplate<size_t>::compact() {
parent->right = item;
}
}
return ox::Error(0);
return {};
});
}
@@ -552,7 +552,7 @@ Error FileStoreTemplate<size_t>::placeItem(ItemPtr item) {
item->left = root->left;
item->right = root->right;
oxTracef("ox.fs.FileStoreTemplate.placeItem", "Overwrote Root Item: {}", item->id.get());
return ox::Error(0);
return {};
} else {
return placeItem(root, item);
}
@@ -573,7 +573,7 @@ Error FileStoreTemplate<size_t>::placeItem(ItemPtr root, ItemPtr item, int depth
item->right = right->right;
}
oxTracef("ox.fs.FileStoreTemplate.placeItem", "Placed Item: {}", item->id.get());
return ox::Error(0);
return {};
} else {
return placeItem(right, item, depth + 1);
}
@@ -586,7 +586,7 @@ Error FileStoreTemplate<size_t>::placeItem(ItemPtr root, ItemPtr item, int depth
item->right = left->right;
}
oxTracef("ox.fs.FileStoreTemplate.placeItem", "Placed Item: {}", item->id.get());
return ox::Error(0);
return {};
} else {
return placeItem(left, item, depth + 1);
}
@@ -624,7 +624,7 @@ Error FileStoreTemplate<size_t>::unplaceItem(ItemPtr item) {
} else {
fsData->rootNode = 0;
}
return ox::Error(0);
return {};
} else {
return unplaceItem(root, item);
}
@@ -661,7 +661,7 @@ Error FileStoreTemplate<size_t>::unplaceItem(ItemPtr root, ItemPtr item, int dep
if (item->left) {
OX_RETURN_ERROR(placeItem(m_buffer->ptr(item->left)));
}
return ox::Error(0);
return {};
}
template<typename size_t>
@@ -669,7 +669,7 @@ Error FileStoreTemplate<size_t>::remove(ItemPtr item) {
if (item.valid()) {
OX_RETURN_ERROR(unplaceItem(item));
OX_RETURN_ERROR(m_buffer->free(item));
return ox::Error(0);
return {};
}
return ox::Error(1);
}

View File

@@ -329,7 +329,7 @@ Error FileSystemTemplate<FileStore, Directory>::format(void *buff, uint64_t buff
return ox::Error(1);
}
return ox::Error(0);
return {};
}
template<typename FileStore, typename Directory>
@@ -346,7 +346,7 @@ Error FileSystemTemplate<FileStore, Directory>::move(StringViewCR src, StringVie
OX_REQUIRE_M(inode, rootDir.find(src));
OX_RETURN_ERROR(rootDir.write(dest, inode));
OX_RETURN_ERROR(rootDir.remove(src));
return ox::Error(0);
return {};
}
template<typename FileStore, typename Directory>
@@ -407,7 +407,7 @@ Error FileSystemTemplate<FileStore, Directory>::removePath(StringViewCR path, bo
oxTrace("FileSystemTemplate.remove.fail", "Tried to remove directory without recursive setting.");
return ox::Error(1);
}
return ox::Error(0);
return {};
}
template<typename FileStore, typename Directory>
@@ -424,7 +424,7 @@ Result<Vector<String>> FileSystemTemplate<FileStore, Directory>::ls(StringViewCR
Vector<String> out;
OX_RETURN_ERROR(ls(path, [&out](StringViewCR name, typename FileStore::InodeId_t) {
out.emplace_back(name);
return ox::Error(0);
return ox::Error{};
}));
return out;
}

View File

@@ -60,7 +60,7 @@ Error PassThroughFS::move(StringViewCR src, StringViewCR dest) noexcept {
if (ec.value()) {
return ox::Error(1);
}
return ox::Error(0);
return {};
}
Result<Vector<String>> PassThroughFS::ls(StringViewCR dir) const noexcept {
@@ -148,7 +148,7 @@ Error PassThroughFS::readFilePath(StringViewCR path, void *buffer, std::size_t b
oxTracef("ox.fs.PassThroughFS.read.error", "Read of {} failed: {}", path, f.what());
return ox::Error(2);
}
return ox::Error(0);
return {};
}
Error PassThroughFS::readFileInode(uint64_t, void*, std::size_t) noexcept {
@@ -197,7 +197,7 @@ Error PassThroughFS::writeFilePath(StringViewCR path, const void *buffer, uint64
oxTracef("ox.fs.PassThroughFS.read.error", "Write of {} failed: {}", path, f.what());
return ox::Error(1);
}
return ox::Error(0);
return {};
}
Error PassThroughFS::writeFileInode(uint64_t, const void*, uint64_t, FileType) noexcept {

View File

@@ -99,7 +99,7 @@ Error PassThroughFS::ls(StringViewCR dir, F cb) const noexcept {
for (auto &p : di) {
OX_RETURN_ERROR(cb(p.path().filename().c_str(), 0));
}
return ox::Error(0);
return {};
}
}

View File

@@ -36,7 +36,7 @@ Error PathIterator::dirPath(char *out, std::size_t outSize) {
if (idx >= 0 && size < outSize) {
ox::memcpy(out, m_path, size);
out[size] = 0;
return ox::Error(0);
return {};
} else {
return ox::Error(1);
}
@@ -85,7 +85,7 @@ Error PathIterator::next(StringView &fileName) {
std::size_t size = 0;
auto retval = ox::Error(1);
if (m_iterator < m_maxSize && ox::strlen(&m_path[m_iterator])) {
retval = ox::Error(0);
retval = {};
if (m_path[m_iterator] == '/') {
m_iterator++;
}
@@ -118,7 +118,7 @@ Result<std::size_t> PathIterator::nextSize() const {
auto retval = ox::Error(1);
auto it = m_iterator;
if (it < m_maxSize && ox::strlen(&m_path[it])) {
retval = ox::Error(0);
retval = {};
if (m_path[it] == '/') {
it++;
}

View File

@@ -354,7 +354,7 @@ Error NodeBuffer<size_t, Item>::free(ItemPtr item) noexcept {
}
}
m_header.bytesUsed -= item.size();
return ox::Error(0);
return {};
}
template<typename size_t, typename Item>
@@ -370,7 +370,7 @@ Error NodeBuffer<size_t, Item>::setSize(std::size_t size) noexcept {
m_header.size = static_cast<size_t>(size);
auto data = reinterpret_cast<uint8_t*>(this) + end;
ox::memset(data, 0, size - end);
return ox::Error(0);
return {};
}
}
@@ -422,7 +422,7 @@ Error NodeBuffer<size_t, Item>::compact(F cb) noexcept {
src = ptr(dest->next);
dest = uninitializedPtr(dest.offset() + dest->fullSize());
}
return ox::Error(0);
return {};
}
template<typename size_t, typename Item>

View File

@@ -48,7 +48,7 @@ static ox::Error runLs(ox::FileSystem *fs, ox::Span<const char*> args) noexcept
for (const auto &file : files) {
oxOutf("{}\n", file);
}
return ox::Error(0);
return {};
}
static ox::Error runRead(ox::FileSystem *fs, ox::Span<const char*> args) noexcept {
@@ -60,7 +60,7 @@ static ox::Error runRead(ox::FileSystem *fs, ox::Span<const char*> args) noexcep
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
std::ignore = fwrite(buff.data(), sizeof(decltype(buff)::value_type), buff.size(), stdout);
OX_ALLOW_UNSAFE_BUFFERS_END
return ox::Error(0);
return {};
}
static ox::Error run(int argc, const char **argv) noexcept {

View File

@@ -91,23 +91,28 @@ ox::Error LoggerConn::sendInit(const InitTraceMsg &msg) noexcept {
}
void LoggerConn::msgSend() noexcept {
while (true) {
std::unique_lock lk(m_waitMut);
m_waitCond.wait(lk);
if (!m_running) {
break;
}
std::lock_guard const buffLk(m_buffMut);
try {
while (true) {
Array<char, units::KB> tmp;
const auto read = m_buff.read(tmp.data(), tmp.size());
if (!read) {
std::unique_lock lk(m_waitMut);
m_waitCond.wait(lk);
if (!m_running) {
break;
}
oxAssert(read <= tmp.size(), "logger trying to read too much data");
//std::printf("LoggerConn: sending %lu bytes\n", read);
std::ignore = send(tmp.data(), read);
std::lock_guard const buffLk(m_buffMut);
while (true) {
Array<char, units::KB> tmp;
const auto read = m_buff.read(tmp.data(), tmp.size());
if (!read) {
break;
}
oxAssert(read <= tmp.size(), "logger trying to read too much data");
//std::printf("LoggerConn: sending %lu bytes\n", read);
std::ignore = send(tmp.data(), read);
}
}
} catch (std::exception const &e) {
oxErrf("Exception in logger thread: {}\n", e.what());
oxAssert(false, "logger thread exception");
}
}

View File

@@ -197,7 +197,7 @@ constexpr Error MetalClawReaderTemplate<Reader>::field(const char*, bool *val) n
OX_RETURN_ERROR(result);
}
++m_field;
return ox::Error(0);
return {};
}
// array handler
@@ -256,7 +256,7 @@ constexpr Error MetalClawReaderTemplate<Reader>::field(const char*, HashMap<Stri
}
}
++m_field;
return ox::Error(0);
return {};
}
template<Reader_c Reader>
@@ -330,7 +330,7 @@ constexpr Error MetalClawReaderTemplate<Reader>::field(const char*, BasicString<
}
}
++m_field;
return ox::Error(0);
return {};
}
template<Reader_c Reader>
@@ -370,7 +370,7 @@ constexpr Error MetalClawReaderTemplate<Reader>::fieldCString(const char*, char
data[size] = 0;
}
++m_field;
return ox::Error(0);
return {};
}
template<Reader_c Reader>
@@ -388,7 +388,7 @@ constexpr Error MetalClawReaderTemplate<Reader>::fieldCString(const char*, char
data[size] = 0;
}
++m_field;
return ox::Error(0);
return {};
}
template<Reader_c Reader>
@@ -416,7 +416,7 @@ constexpr Error MetalClawReaderTemplate<Reader>::fieldCString(const char*, char
}
}
++m_field;
return ox::Error(0);
return {};
}
template<Reader_c Reader>
@@ -464,7 +464,7 @@ constexpr Error MetalClawReaderTemplate<Reader>::readInteger(I *val) noexcept {
}
}
++m_field;
return ox::Error(0);
return {};
}
template<Reader_c Reader>
@@ -487,7 +487,7 @@ constexpr Error MetalClawReaderTemplate<Reader>::field(const char*, CB cb) noexc
}
}
++m_field;
return ox::Error(0);
return {};
}
template<Reader_c Reader>

View File

@@ -122,7 +122,7 @@ class MetalClawWriter {
}
OX_RETURN_ERROR(m_fieldPresence.set(static_cast<std::size_t>(m_field), fieldSet));
++m_field;
return ox::Error(0);
return {};
}
};
@@ -184,7 +184,7 @@ constexpr Error MetalClawWriter<Writer>::field(const char*, const bool *val) noe
OX_RETURN_ERROR(m_fieldPresence.set(static_cast<std::size_t>(m_field), *val));
}
++m_field;
return ox::Error(0);
return {};
}
template<Writer_c Writer>
@@ -201,7 +201,7 @@ constexpr Error MetalClawWriter<Writer>::field(const char*, const BasicString<Sm
}
OX_RETURN_ERROR(m_fieldPresence.set(static_cast<std::size_t>(m_field), fieldSet));
++m_field;
return ox::Error(0);
return {};
}
template<Writer_c Writer>
@@ -229,7 +229,7 @@ constexpr Error MetalClawWriter<Writer>::fieldCString(const char*, const char *c
}
OX_RETURN_ERROR(m_fieldPresence.set(static_cast<std::size_t>(m_field), fieldSet));
++m_field;
return ox::Error(0);
return {};
}
template<Writer_c Writer>
@@ -255,7 +255,7 @@ constexpr Error MetalClawWriter<Writer>::fieldCString(const char*, const char *v
}
OX_RETURN_ERROR(m_fieldPresence.set(static_cast<std::size_t>(m_field), fieldSet));
++m_field;
return ox::Error(0);
return {};
}
template<Writer_c Writer>
@@ -319,7 +319,7 @@ OX_ALLOW_UNSAFE_BUFFERS_END
}
OX_RETURN_ERROR(m_fieldPresence.set(static_cast<std::size_t>(m_field), fieldSet));
++m_field;
return ox::Error(0);
return {};
}
template<Writer_c Writer>
@@ -356,7 +356,7 @@ constexpr Error MetalClawWriter<Writer>::field(const char*, const HashMap<String
}
OX_RETURN_ERROR(m_fieldPresence.set(static_cast<std::size_t>(m_field), fieldSet));
++m_field;
return ox::Error(0);
return {};
}
template<Writer_c Writer>

View File

@@ -13,7 +13,7 @@
// oxModelFwdDecl is necessary because Apple-Clang is broken...
#define OX_MODEL_FWD_DECL(modelName) constexpr ox::Error model(auto *io, ox::CommonPtrWith<modelName> auto *o) noexcept
#define OX_MODEL_BEGIN(modelName) constexpr ox::Error model(auto *io, [[maybe_unused]] ox::CommonPtrWith<modelName> auto *o) noexcept { OX_RETURN_ERROR(io->template setTypeInfo<modelName>());
#define OX_MODEL_END() return ox::Error(0); }
#define OX_MODEL_END() return {}; }
#define OX_MODEL_FIELD(fieldName) OX_RETURN_ERROR(io->field(#fieldName, &o->fieldName));
#define OX_MODEL_FIELD_RENAME(objFieldName, serFieldName) OX_RETURN_ERROR(io->field(#serFieldName, &o->objFieldName));
#define OX_MODEL_FRIEND(modelName) friend constexpr ox::Error model(auto *io, ox::CommonPtrWith<modelName> auto *o) noexcept

View File

@@ -244,7 +244,7 @@ constexpr Error model(TypeDescReader<T> *io, CommonPtrWith<DescriptorField> auto
// defaultValue is unused now, but placeholder for backwards compatibility
int defaultValue = 0;
oxReturnError(io->field("defaultValue", &defaultValue));
return ox::Error(0);
return {};
}
#endif

View File

@@ -205,7 +205,7 @@ constexpr Error TypeDescWriter::field(StringViewCR name, T const*, std::size_t,
const auto t = type(p);
oxAssert(t != nullptr, "field(const char *name, T *val, std::size_t): Type not found or generated");
m_type->fieldList.emplace_back(t, String(name), detail::indirectionLevels_v<T> + 1, subscriptStack, buildTypeId(*t));
return ox::Error(0);
return {};
}
return ox::Error(1);
}
@@ -220,7 +220,7 @@ constexpr Error TypeDescWriter::field(StringViewCR name, T const*, std::size_t)
auto const lvls = detail::indirectionLevels_v<T> + 1;
SubscriptStack subscriptStack{lvls};
m_type->fieldList.emplace_back(t, String(name), lvls, subscriptStack, buildTypeId(*t));
return ox::Error(0);
return {};
}
return ox::Error(1);
}
@@ -231,7 +231,7 @@ constexpr Error TypeDescWriter::field(StringViewCR name, UnionView<T, force> val
const auto t = type(val);
oxAssert(t != nullptr, "field(const char *name, T val): Type not found or generated");
m_type->fieldList.emplace_back(t, String(name), 0, SubscriptStack{}, ox::String(t->typeName));
return ox::Error(0);
return {};
}
return ox::Error(1);
}

View File

@@ -31,25 +31,25 @@ class FieldCounter {
template<typename U>
constexpr ox::Error field(StringViewCR, U) noexcept {
++fields;
return ox::Error(0);
return {};
}
template<typename U>
constexpr ox::Error field(StringViewCR, U, std::size_t) noexcept {
++fields;
return ox::Error(0);
return {};
}
template<typename U, typename Handler>
constexpr Error field(StringViewCR, Handler) {
++fields;
return ox::Error(0);
return {};
}
template<typename ...Args>
constexpr Error fieldCString(Args&&...) noexcept {
++fields;
return ox::Error(0);
return {};
}
static constexpr auto opType() noexcept {

View File

@@ -56,19 +56,19 @@ class MemberList {
template<typename T>
constexpr Error field(const char*, T *v) noexcept {
vars[m_i++] = static_cast<void*>(v);
return ox::Error(0);
return {};
}
template<typename T>
constexpr Error field(const char*, T *v, int) noexcept {
vars[m_i++] = static_cast<void*>(v);
return ox::Error(0);
return {};
}
template<typename U, bool force = false>
constexpr Error field(const char*, UnionView<U, force> u) noexcept {
vars[m_i++] = static_cast<void*>(u.get());
return ox::Error(0);
return {};
}
template<typename T>
@@ -107,7 +107,7 @@ class Copier {
auto &dst = *cbit_cast<FT*>(m_dst->vars[m_i]);
dst = src;
++m_i;
return ox::Error(0);
return {};
}
}
@@ -119,7 +119,7 @@ class Copier {
dst = src;
}
++m_i;
return ox::Error(0);
return {};
}
template<typename U, bool force = false>
@@ -128,7 +128,7 @@ class Copier {
auto &src = *u.get();
dst = src;
++m_i;
return ox::Error(0);
return {};
}
template<typename T = void>
@@ -168,7 +168,7 @@ class Mover {
dst = std::move(src);
src = FT{};
++m_i;
return ox::Error(0);
return {};
}
}
@@ -181,7 +181,7 @@ class Mover {
src = FT{};
}
++m_i;
return ox::Error(0);
return {};
}
template<typename U, bool force = false>
@@ -190,7 +190,7 @@ class Mover {
auto &src = *u.get();
dst = std::move(src);
++m_i;
return ox::Error(0);
return {};
}
template<typename T = void>
@@ -228,7 +228,7 @@ class Equals {
const auto &dst = std::bit_cast<FT>(*m_other->vars[m_i]);
++m_i;
if (dst == src) {
return ox::Error(0);
return {};
} else {
this->value = false;
return ox::Error(1);
@@ -246,7 +246,7 @@ class Equals {
}
}
++m_i;
return ox::Error(0);
return {};
}
template<typename U, bool force = false>
@@ -255,7 +255,7 @@ class Equals {
const auto &src = *u.get();
++m_i;
if (dst == src) {
return ox::Error(0);
return {};
} else {
this->value = false;
return ox::Error(1);

View File

@@ -18,6 +18,6 @@ static_assert([]() -> ox::Error {
}
//oxReturnError(v.set<int32_t>(5));
return {};
}() == ox::Error(0));
}() == ox::Error{});
}

View File

@@ -972,7 +972,7 @@ constexpr Error model(auto *h, CommonPtrWith<ModelObject> auto *obj) noexcept {
for (auto &f : obj->m_fieldsOrder) {
OX_RETURN_ERROR(h->field(f->name.c_str(), &f->value));
}
return ox::Error(0);
return {};
}
constexpr Error model(auto *h, CommonPtrWith<ModelUnion> auto *obj) noexcept {
@@ -981,7 +981,7 @@ constexpr Error model(auto *h, CommonPtrWith<ModelUnion> auto *obj) noexcept {
for (auto &f : obj->m_fieldsOrder) {
OX_RETURN_ERROR(h->field(f->name.c_str(), &f->value));
}
return ox::Error(0);
return {};
}
constexpr ModelValue::ModelValue(const ModelValue &other) noexcept {
@@ -1129,7 +1129,7 @@ constexpr Error ModelValue::setType(
OX_RETURN_ERROR(m_data.uni->setType(type));
}
oxAssert(m_type != Type::Undefined, "No type set");
return ox::Error(0);
return {};
}
template<typename T>
@@ -1184,7 +1184,7 @@ constexpr Error ModelValue::set(const T &v) noexcept {
safeDelete(&value);
}
value = v;
return ox::Error(0);
return {};
}
template<typename T>
@@ -1199,7 +1199,7 @@ constexpr Error ModelValue::set(T &&v) noexcept {
safeDelete(&value);
}
value = std::move(v);
return ox::Error(0);
return {};
}
constexpr ModelValue &ModelValue::operator=(ModelValue &other) noexcept {

View File

@@ -38,17 +38,17 @@ struct TypeNameCatcher {
template<typename T>
constexpr Error field(const char*, T*, std::size_t) noexcept {
return ox::Error(0);
return {};
}
template<typename T>
constexpr Error field(const char*, T) noexcept {
return ox::Error(0);
return {};
}
template<typename ...Args>
constexpr Error fieldCString(Args&&...) noexcept {
return ox::Error(0);
return {};
}
static constexpr auto opType() noexcept {
@@ -77,17 +77,17 @@ struct TypeInfoCatcher {
template<typename T>
constexpr Error field(const char*, T*, std::size_t) noexcept {
return ox::Error(0);
return {};
}
template<typename T>
constexpr Error field(const char*, T) noexcept {
return ox::Error(0);
return {};
}
template<typename T>
constexpr Error fieldCString(const char*, T) noexcept {
return ox::Error(0);
return {};
}
static constexpr auto opType() noexcept {

View File

@@ -127,7 +127,7 @@ static constexpr Error parseField(const DescriptorField &field, Reader *rdr, Dat
}
}
walker->popNamePath();
return ox::Error(0);
return {};
}
template<typename Reader, typename FH>
@@ -141,7 +141,7 @@ constexpr Error model(Reader *rdr, DataWalker<Reader, FH> *walker) noexcept {
for (const auto &field : fields) {
OX_RETURN_ERROR(parseField(field, rdr, walker));
}
return ox::Error(0);
return {};
}
template<typename Reader, typename Handler>

View File

@@ -37,7 +37,7 @@ OrganicClawReader::OrganicClawReader(Json::Value json, int unionIdx) noexcept:
}
Error OrganicClawReader::field(const char *key, bool *val) noexcept {
auto err = ox::Error(0);
ox::Error err{};
if (targetValid()) {
const auto &jv = value(key);
if (jv.empty()) {
@@ -53,7 +53,7 @@ Error OrganicClawReader::field(const char *key, bool *val) noexcept {
}
Error OrganicClawReader::fieldCString(const char *key, char *val, std::size_t buffLen) noexcept {
auto err = ox::Error(0);
ox::Error err{};
const char *begin = nullptr, *end = nullptr;
const auto &jv = value(key);
if (targetValid()) {
@@ -81,7 +81,7 @@ Error OrganicClawReader::fieldCString(const char *key, char *val, std::size_t bu
}
Error OrganicClawReader::fieldCString(const char *key, char **val) noexcept {
auto err = ox::Error(0);
ox::Error err{};
const char *begin = nullptr, *end = nullptr;
const auto &jv = value(key);
auto &data = *val;
@@ -106,7 +106,7 @@ Error OrganicClawReader::fieldCString(const char *key, char **val) noexcept {
}
Error OrganicClawReader::fieldCString(const char *key, char **val, std::size_t buffLen) noexcept {
auto err = ox::Error(0);
ox::Error err{};
const char *begin = nullptr, *end = nullptr;
const auto &jv = value(key);
if (targetValid()) {

View File

@@ -137,7 +137,7 @@ class OrganicClawReader {
template<typename T>
Error OrganicClawReader::field(const char *key, T *val) noexcept {
auto err = ox::Error(0);
ox::Error err{};
try {
if constexpr (is_integer_v<T>) {
if (targetValid()) {
@@ -190,7 +190,7 @@ Error OrganicClawReader::field(const char *key, T *val) noexcept {
template<typename U, bool force>
Error OrganicClawReader::field(const char *key, UnionView<U, force> val) noexcept {
auto err = ox::Error(0);
ox::Error err{};
if (targetValid()) {
const auto &jv = value(key);
if (jv.empty() || jv.isObject()) {
@@ -207,7 +207,7 @@ Error OrganicClawReader::field(const char *key, UnionView<U, force> val) noexcep
template<std::size_t L>
Error OrganicClawReader::field(const char *key, BasicString<L> *val) noexcept {
auto err = ox::Error(0);
ox::Error err{};
if (targetValid()) {
const auto &jv = value(key);
if (jv.empty()) {
@@ -224,7 +224,7 @@ Error OrganicClawReader::field(const char *key, BasicString<L> *val) noexcept {
template<std::size_t L>
Error OrganicClawReader::field(const char *key, IString<L> *val) noexcept {
auto err = ox::Error(0);
ox::Error err{};
if (targetValid()) {
const auto &jv = value(key);
if (jv.empty()) {
@@ -257,7 +257,7 @@ OX_ALLOW_UNSAFE_BUFFERS_BEGIN
OX_RETURN_ERROR(handler.field("", &val[i]));
OX_ALLOW_UNSAFE_BUFFERS_END
}
return ox::Error(0);
return {};
}
template<typename T>
@@ -274,7 +274,7 @@ Error OrganicClawReader::field(const char *key, HashMap<String, T> *val) noexcep
const auto k = keys[i].c_str();
OX_RETURN_ERROR(handler.field(k, &val->operator[](k)));
}
return ox::Error(0);
return {};
}
Error readOC(BufferView buff, auto &val) noexcept {

View File

@@ -23,7 +23,7 @@ Error OrganicClawWriter::fieldCString(const char *key, const char *const*val, in
value(key) = *val;
}
++m_fieldIt;
return ox::Error(0);
return {};
}
Error OrganicClawWriter::fieldCString(const char *key, const char *const*val) noexcept {

View File

@@ -46,7 +46,7 @@ class OrganicClawWriter {
value(key) = *val;
}
++m_fieldIt;
return ox::Error(0);
return {};
}
Error field(const char *key, const int16_t *val) noexcept {
@@ -54,7 +54,7 @@ class OrganicClawWriter {
value(key) = *val;
}
++m_fieldIt;
return ox::Error(0);
return {};
}
Error field(const char *key, const int32_t *val) noexcept {
@@ -62,7 +62,7 @@ class OrganicClawWriter {
value(key) = *val;
}
++m_fieldIt;
return ox::Error(0);
return {};
}
Error field(const char *key, const int64_t *val) noexcept {
@@ -70,7 +70,7 @@ class OrganicClawWriter {
value(key) = *val;
}
++m_fieldIt;
return ox::Error(0);
return {};
}
@@ -79,7 +79,7 @@ class OrganicClawWriter {
value(key) = *val;
}
++m_fieldIt;
return ox::Error(0);
return {};
}
Error field(const char *key, const uint16_t *val) noexcept {
@@ -87,7 +87,7 @@ class OrganicClawWriter {
value(key) = *val;
}
++m_fieldIt;
return ox::Error(0);
return {};
}
Error field(const char *key, const uint32_t *val) noexcept {
@@ -95,7 +95,7 @@ class OrganicClawWriter {
value(key) = *val;
}
++m_fieldIt;
return ox::Error(0);
return {};
}
Error field(const char *key, const uint64_t *val) noexcept {
@@ -103,7 +103,7 @@ class OrganicClawWriter {
value(key) = *val;
}
++m_fieldIt;
return ox::Error(0);
return {};
}
Error field(char const*key, bool const*val) noexcept {
@@ -151,7 +151,7 @@ class OrganicClawWriter {
value(key) = val->c_str();
}
++m_fieldIt;
return ox::Error(0);
return {};
}
Error fieldCString(const char*, const char *const*val, int len) noexcept;
@@ -211,7 +211,7 @@ OX_ALLOW_UNSAFE_BUFFERS_END
value(key) = w.m_json;
}
++m_fieldIt;
return ox::Error(0);
return {};
}
template<typename T>
@@ -237,7 +237,7 @@ Error OrganicClawWriter::field(const char *key, const T *val) noexcept {
}
}
++m_fieldIt;
return ox::Error(0);
return {};
}
template<typename U, bool force>
@@ -251,7 +251,7 @@ Error OrganicClawWriter::field(const char *key, UnionView<U, force> val) noexcep
}
}
++m_fieldIt;
return ox::Error(0);
return {};
}
Result<ox::Buffer> writeOC(const auto &val) noexcept {

View File

@@ -182,7 +182,7 @@ class BasicString {
// make sure last element is a null terminator
m_buff[currentLen + strLen] = 0;
// this can't fail, but it returns an Error to match BString::append
return ox::Error(0);
return {};
}
constexpr Error append(ox::StringView sv) noexcept {

View File

@@ -104,13 +104,16 @@ constexpr ox::Result<int> strToInt(StringViewCR str) noexcept {
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
int total = 0;
int multiplier = 1;
if (str.len() == 0) [[unlikely]] {
return Error{1, "Empty string passed to strToInt"};
}
for (auto i = static_cast<int64_t>(str.len()) - 1; i != -1; --i) {
auto s = static_cast<std::size_t>(i);
if (str[s] >= '0' && str[s] <= '9') {
total += (str[s] - '0') * multiplier;
multiplier *= 10;
} else {
return ox::Error(1);
return ox::Error{1};
}
}
return total;

View File

@@ -311,7 +311,7 @@ class Vector: detail::VectorAllocator<T, Allocator, SmallVectorSize> {
*/
constexpr Error unordered_erase(std::size_t pos) noexcept(useNoexcept);
constexpr Error remove(T const &val);
constexpr Error remove(MaybeView_t<T> const &val);
constexpr void reserve(std::size_t cap) noexcept(useNoexcept);
@@ -658,11 +658,11 @@ constexpr Error Vector<T, SmallVectorSize, Allocator>::unordered_erase(std::size
--m_size;
m_items[pos] = std::move(m_items[m_size]);
m_items[m_size].~T();
return ox::Error(0);
return {};
}
template<typename T, std::size_t SmallVectorSize, typename Allocator>
constexpr ox::Error Vector<T, SmallVectorSize, Allocator>::remove(T const &val) {
constexpr ox::Error Vector<T, SmallVectorSize, Allocator>::remove(MaybeView_t<T> const &val) {
for (size_t i{}; auto const &v : *this) {
if (v == val) {
return erase(i).error;

View File

@@ -1,6 +1,19 @@
# d2025.06.0
* Add ability to remember recent projects in config
* Add navigation support (back and forward)
* Fix file deletion to close file even if not active
* Fix file copy to work when creating a copy with the name of a previously
deleted file
* Fix crash that could occur after switching projects
* Make file picker popup accept on double click of a file
* TileSheetEditor: Fix copy/cut/paste enablement when there is no selection
* TileSheetEditor: Fix manual redo of draw actions, fix drawing to pixel 0, 0
as first action
* TileSheetEditor: Fix draw command to work on same pixel after switching
subsheets
* PaletteEditor: Add RGB key shortcuts for focusing color channels
* PaletteEditor: Add color preview to color editor
# d2025.05.1

View File

@@ -142,9 +142,9 @@ constexpr Color16 color16(int r, int g, int b, int a = 0) noexcept {
[[nodiscard]]
constexpr Color16 color16(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 0) noexcept {
return static_cast<Color16>(ox::min<uint8_t>(r, 31))
| static_cast<Color16>(ox::min<uint8_t>(g, 31) << 5)
| static_cast<Color16>(ox::min<uint8_t>(b, 31) << 10)
| static_cast<Color16>(a << 15);
| static_cast<Color16>(ox::min<uint8_t>(g, 31) << 5)
| static_cast<Color16>(ox::min<uint8_t>(b, 31) << 10)
| static_cast<Color16>(a << 15);
}
static_assert(color16(0, 31, 0) == 992);

View File

@@ -350,7 +350,7 @@ void panic(const char *file, int line, const char *panicMsg, ox::Error const&err
const auto heapEnd = reinterpret_cast<char*>(MEM_EWRAM_BEGIN + heapSz);
ox::heapmgr::initHeap(heapBegin, heapEnd);
OX_ALLOW_UNSAFE_BUFFERS_END
auto tctx = turbine::init(keel::loadRomFs("").unwrap(), "Nostalgia").unwrap();
auto tctx = turbine::init(keel::loadRomFs("").unwrap(), "Nostalgia").unwrap();
auto ctx = init(*tctx).unwrap();
std::ignore = initGfx();
std::ignore = initConsole(*ctx);

View File

@@ -8,7 +8,7 @@
namespace nostalgia::gfx {
RemovePageCommand::RemovePageCommand(Palette &pal, size_t idx) noexcept:
RemovePageCommand::RemovePageCommand(Palette &pal, size_t const idx) noexcept:
m_pal(pal),
m_idx(idx) {}

View File

@@ -9,13 +9,13 @@ namespace nostalgia::gfx {
UpdateColorCommand::UpdateColorCommand(
Palette &pal,
size_t page,
size_t idx,
Color16 newColor):
m_pal(pal),
m_page(page),
m_idx(idx),
m_altColor(newColor) {
size_t const page,
size_t const idx,
Color16 const 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();
}

View File

@@ -302,29 +302,43 @@ void PaletteEditorImGui::drawColorEditor() noexcept {
int const a = alpha16(c);
auto const newName = ig::InputText<50>(
"Name", m_pal.colorNames[m_selectedColorRow]);
bool inputFocused = ImGui::IsItemFocused();
auto const nameInputFocused = ImGui::IsItemFocused();
ImGui::Separator();
colorInput("Red", r, inputFocused, FocusCmd::Red);
colorInput("Green", g, inputFocused, FocusCmd::Green);
colorInput("Blue", b, inputFocused, FocusCmd::Blue);
bool colorInFocused{};
colorInput("Red", r, colorInFocused, FocusCmd::Red);
colorInput("Green", g, colorInFocused, FocusCmd::Green);
colorInput("Blue", b, colorInFocused, FocusCmd::Blue);
// color preview
{
ImGui::PushStyleColor(
ImGuiCol_ChildBg,
color32(color16(r, g, b)) | 0xff'00'00'00);
ImGui::BeginChild("ColorPreview", {0, 25});
ImGui::EndChild();
ImGui::PopStyleColor();
}
if (ig::PushButton("Apply to all pages", {-1, ig::BtnSz.y})) {
std::ignore = pushCommand<ApplyColorAllPagesCommand>(
m_pal, m_page, m_selectedColorRow);
}
if (ig::mainWinHasFocus() && !inputFocused && !ImGui::IsKeyDown(ImGuiKey_ModCtrl)) {
if (ig::mainWinHasFocus() && !ImGui::IsKeyDown(ImGuiKey_ModCtrl)) {
if (!ImGui::IsKeyDown(ImGuiKey_ModAlt)) {
numShortcuts(m_selectedColorRow, largestPage(m_pal));
if (!nameInputFocused && !colorInFocused) {
numShortcuts(m_selectedColorRow, largestPage(m_pal));
}
} else {
numShortcuts(m_page, m_pal.pages.size());
}
if (ImGui::IsKeyDown(ImGuiKey_R)) {
m_focusCmd = FocusCmd::Red;
}
if (ImGui::IsKeyDown(ImGuiKey_G)) {
m_focusCmd = FocusCmd::Green;
}
if (ImGui::IsKeyDown(ImGuiKey_B)) {
m_focusCmd = FocusCmd::Blue;
if (!nameInputFocused) {
if (ImGui::IsKeyDown(ImGuiKey_R)) {
m_focusCmd = FocusCmd::Red;
}
if (ImGui::IsKeyDown(ImGuiKey_G)) {
m_focusCmd = FocusCmd::Green;
}
if (ImGui::IsKeyDown(ImGuiKey_B)) {
m_focusCmd = FocusCmd::Blue;
}
}
}
auto const newColor = color16(r, g, b, a);

View File

@@ -13,7 +13,7 @@ AddSubSheetCommand::AddSubSheetCommand(
auto &parent = getSubSheet(m_img, m_parentIdx);
if (!parent.subsheets.empty()) {
auto idx = m_parentIdx;
idx.emplace_back(parent.subsheets.size());
idx.emplace_back(static_cast<uint32_t>(parent.subsheets.size()));
m_addedSheets.push_back(idx);
} else {
auto idx = m_parentIdx;

View File

@@ -13,20 +13,19 @@ DeleteTilesCommand::DeleteTilesCommand(
TileSheet::SubSheetIdx idx,
std::size_t const tileIdx,
std::size_t const tileCnt) noexcept:
m_img(img),
m_idx(std::move(idx)) {
constexpr unsigned bytesPerTile = PixelsPerTile;
m_deletePos = tileIdx * bytesPerTile;
m_deleteSz = tileCnt * bytesPerTile;
m_deletedPixels.resize(m_deleteSz);
// copy pixels to be erased
{
auto &s = getSubSheet(m_img, m_idx);
auto dst = m_deletedPixels.begin();
auto src = s.pixels.begin() + m_deletePos;
ox::copy_n(src, m_deleteSz, dst);
}
}
m_img{img},
m_idx{std::move(idx)},
m_deletePos{tileIdx * PixelsPerTile},
m_deleteSz{tileCnt * PixelsPerTile},
m_deletedPixels{[this] {
ox::Vector<uint8_t> deletedPixels(m_deleteSz);
// copy pixels to be erased
auto const &s = getSubSheet(m_img, m_idx);
auto const dst = deletedPixels.begin();
auto const src = s.pixels.begin() + m_deletePos;
ox::copy_n(src, m_deleteSz, dst);
return deletedPixels;
}()} {}
ox::Error DeleteTilesCommand::redo() noexcept {
auto &s = getSubSheet(m_img, m_idx);

View File

@@ -11,10 +11,10 @@ namespace nostalgia::gfx {
class DeleteTilesCommand: public TileSheetCommand {
private:
TileSheet &m_img;
TileSheet::SubSheetIdx m_idx;
std::size_t m_deletePos = 0;
std::size_t m_deleteSz = 0;
ox::Vector<uint8_t> m_deletedPixels = {};
TileSheet::SubSheetIdx const m_idx;
std::size_t const m_deletePos = 0;
std::size_t const m_deleteSz = 0;
ox::Vector<uint8_t> const m_deletedPixels;
public:
DeleteTilesCommand(

View File

@@ -62,11 +62,11 @@ constexpr void iterateLine(ox::Point const &a, ox::Point const &b, auto const &f
DrawCommand::DrawCommand(
TileSheet &img,
TileSheet::SubSheetIdx subSheetIdx,
std::size_t idx,
std::size_t const idx,
int const palIdx) noexcept:
m_img(img),
m_subSheetIdx(std::move(subSheetIdx)),
m_palIdx(palIdx) {
m_img{img},
m_subSheetIdx{std::move(subSheetIdx)},
m_palIdx{palIdx} {
auto &subsheet = getSubSheet(m_img, m_subSheetIdx);
m_changes.emplace_back(static_cast<uint32_t>(idx), getPixel(subsheet, idx));
}

View File

@@ -12,19 +12,19 @@ InsertTilesCommand::InsertTilesCommand(
std::size_t const tileIdx,
std::size_t const tileCnt) noexcept:
m_img{img},
m_idx{std::move(idx)} {
m_insertPos = tileIdx * PixelsPerTile;
m_insertCnt = tileCnt * PixelsPerTile;
m_deletedPixels.resize(m_insertCnt);
// copy pixels to be erased
{
auto &s = getSubSheet(m_img, m_idx);
auto &p = s.pixels;
auto const dst = m_deletedPixels.begin();
auto const src = p.begin() + p.size() - m_insertCnt;
ox::copy_n(src, m_insertCnt, dst);
}
}
m_idx{std::move(idx)},
m_insertPos{tileIdx * PixelsPerTile},
m_insertCnt{tileCnt * PixelsPerTile},
m_insertedPixels{[this] {
ox::Vector<uint8_t> insertedPixels(m_insertCnt);
// copy pixels to be erased
auto &s = getSubSheet(m_img, m_idx);
auto &p = s.pixels;
auto const dst = insertedPixels.begin();
auto const src = p.begin() + p.size() - m_insertCnt;
ox::copy_n(src, m_insertCnt, dst);
return insertedPixels;
}()} {}
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
@@ -52,7 +52,7 @@ ox::Error InsertTilesCommand::undo() noexcept {
auto const src = &p[srcIdx];
ox::memmove(dst1, src, sz);
}
ox::memcpy(dst2, m_deletedPixels.data(), m_deletedPixels.size());
ox::memcpy(dst2, m_insertedPixels.data(), m_insertedPixels.size());
return {};
}

View File

@@ -11,10 +11,10 @@ namespace nostalgia::gfx {
class InsertTilesCommand: public TileSheetCommand {
private:
TileSheet &m_img;
TileSheet::SubSheetIdx m_idx;
std::size_t m_insertPos = 0;
std::size_t m_insertCnt = 0;
ox::Vector<uint8_t> m_deletedPixels = {};
TileSheet::SubSheetIdx const m_idx;
std::size_t const m_insertPos{};
std::size_t const m_insertCnt{};
ox::Vector<uint8_t> const m_insertedPixels;
public:
InsertTilesCommand(

View File

@@ -15,7 +15,6 @@ UpdateSubSheetCommand::UpdateSubSheetCommand(
m_img{img},
m_idx{std::move(idx)},
m_sheet{getSubSheet(m_img, m_idx)} {
m_sheet = getSubSheet(m_img, m_idx);
m_sheet.name = std::move(name);
OX_THROW_ERROR(resizeSubsheet(m_sheet, {cols, rows}));
}

View File

@@ -11,7 +11,7 @@ namespace nostalgia::gfx {
class UpdateSubSheetCommand: public TileSheetCommand {
private:
TileSheet &m_img;
TileSheet::SubSheetIdx m_idx;
TileSheet::SubSheetIdx const m_idx;
TileSheet::SubSheet m_sheet;
public:

View File

@@ -36,7 +36,7 @@ OX_MODEL_BEGIN(TileSheetEditorConfig)
OX_MODEL_END()
static ox::Vector<uint32_t> normalizePixelSizes(
ox::Vector<uint8_t> const&inPixels) noexcept {
ox::Vector<uint8_t> const &inPixels) noexcept {
ox::Vector<uint32_t> outPixels;
outPixels.reserve(inPixels.size());
outPixels.resize(inPixels.size());
@@ -47,7 +47,7 @@ static ox::Vector<uint32_t> normalizePixelSizes(
}
static ox::Vector<uint32_t> normalizePixelArrangement(
ox::Vector<uint32_t> const&inPixels,
ox::Vector<uint32_t> const &inPixels,
int const cols,
int const scale) {
auto const scalePt = ox::Point{scale, scale};
@@ -67,10 +67,10 @@ static ox::Vector<uint32_t> normalizePixelArrangement(
}
static ox::Error toPngFile(
ox::CStringView const&path,
ox::CStringViewCR path,
ox::Vector<uint32_t> &&pixels,
Palette const&pal,
size_t page,
Palette const &pal,
size_t const page,
unsigned const width,
unsigned const height) noexcept {
for (auto &c : pixels) {
@@ -88,7 +88,7 @@ static ox::Error toPngFile(
}
TileSheetEditorImGui::TileSheetEditorImGui(studio::Context &sctx, ox::StringParam path):
Editor(sctx, std::move(path)),
Editor{sctx, std::move(path)},
m_sctx{sctx},
m_tctx{m_sctx.tctx},
m_palPicker{"Palette Chooser", keelCtx(sctx), FileExt_npal},
@@ -98,8 +98,11 @@ TileSheetEditorImGui::TileSheetEditorImGui(studio::Context &sctx, ox::StringPara
m_subsheetEditor.inputSubmitted.connect(this, &TileSheetEditorImGui::updateActiveSubsheet);
m_exportMenu.inputSubmitted.connect(this, &TileSheetEditorImGui::exportSubsheetToPng);
// load config
auto const&config = studio::readConfig<TileSheetEditorConfig>(
keelCtx(m_sctx), itemPath());
auto &kctx = keelCtx(m_sctx);
auto const ip = itemPath();
oxLogError(studio::headerizeConfigFile<TileSheetEditorConfig>(kctx, ip));
auto const &config =
studio::readConfig<TileSheetEditorConfig>(kctx, ip);
if (config.ok()) {
m_model.setActiveSubsheet(validateSubSheetIdx(m_model.img(), config.value.activeSubsheet));
}
@@ -189,10 +192,13 @@ void TileSheetEditorImGui::keyStateChanged(turbine::Key const key, bool const do
}
void TileSheetEditorImGui::draw(studio::Context&) noexcept {
setCopyEnabled(m_model.hasSelection());
setCutEnabled(m_model.hasSelection());
setPasteEnabled(m_model.hasSelection());
if (ig::mainWinHasFocus() && m_tool == TileSheetTool::Select) {
if (ImGui::IsKeyDown(ImGuiKey_ModCtrl) && !m_palPathFocused) {
if (ImGui::IsKeyPressed(ImGuiKey_A)) {
auto const&img = m_model.activeSubSheet();
auto const &img = m_model.activeSubSheet();
m_model.setSelection({{}, {img.columns * TileWidth - 1, img.rows * TileHeight - 1}});
} else if (ImGui::IsKeyPressed(ImGuiKey_G)) {
m_model.clearSelection();
@@ -271,14 +277,14 @@ void TileSheetEditorImGui::draw(studio::Context&) noexcept {
auto constexpr btnSize = ImVec2{btnHeight, btnHeight};
if (ig::PushButton("+", btnSize)) {
auto insertOnIdx = m_model.activeSubSheetIdx();
auto const&parent = m_model.activeSubSheet();
auto const &parent = m_model.activeSubSheet();
m_model.addSubsheet(insertOnIdx);
insertOnIdx.emplace_back(parent.subsheets.size() - 1);
insertOnIdx.emplace_back(static_cast<uint32_t>(parent.subsheets.size() - 1));
setActiveSubsheet(insertOnIdx);
}
ImGui::SameLine();
if (ig::PushButton("-", btnSize)) {
auto const&activeSubsheetIdx = m_model.activeSubSheetIdx();
auto const &activeSubsheetIdx = m_model.activeSubSheetIdx();
if (!activeSubsheetIdx.empty()) {
m_model.rmSubsheet(activeSubsheetIdx);
}
@@ -378,7 +384,7 @@ void TileSheetEditorImGui::drawSubsheetSelector(
}
[[nodiscard]]
ox::Vec2 TileSheetEditorImGui::clickPos(ImVec2 const&winPos, ox::Vec2 clickPos) noexcept {
ox::Vec2 TileSheetEditorImGui::clickPos(ImVec2 const &winPos, ox::Vec2 clickPos) noexcept {
clickPos.x -= winPos.x + 10;
clickPos.y -= winPos.y + 10;
return clickPos;
@@ -400,7 +406,7 @@ void TileSheetEditorImGui::navigateTo(ox::StringViewCR arg) noexcept {
}
void TileSheetEditorImGui::showSubsheetEditor() noexcept {
auto const&sheet = m_model.activeSubSheet();
auto const &sheet = m_model.activeSubSheet();
if (!sheet.subsheets.empty()) {
m_subsheetEditor.show(sheet.name, -1, -1);
} else {
@@ -411,8 +417,8 @@ void TileSheetEditorImGui::showSubsheetEditor() noexcept {
ox::Error TileSheetEditorImGui::exportSubsheetToPng(int const scale) const noexcept {
OX_REQUIRE(path, studio::saveFile({{"PNG", "png"}}));
// subsheet to png
auto const&s = m_model.activeSubSheet();
auto const&pal = m_model.pal();
auto const &s = m_model.activeSubSheet();
auto const &pal = m_model.pal();
auto const width = s.columns * TileWidth;
auto const height = s.rows * TileHeight;
auto pixels = normalizePixelSizes(s.pixels);
@@ -430,7 +436,7 @@ ox::Error TileSheetEditorImGui::exportSubsheetToPng(int const scale) const noexc
return err;
}
void TileSheetEditorImGui::drawTileSheet(ox::Vec2 const&fbSize) noexcept {
void TileSheetEditorImGui::drawTileSheet(ox::Vec2 const &fbSize) noexcept {
auto const winPos = ImGui::GetWindowPos();
auto const fbSizei = ox::Size{static_cast<int>(fbSize.x), static_cast<int>(fbSize.y)};
if (m_framebuffer.width != fbSizei.width || m_framebuffer.height != fbSizei.height) {
@@ -451,7 +457,7 @@ void TileSheetEditorImGui::drawTileSheet(ox::Vec2 const&fbSize) noexcept {
{0, 1},
{1, 0});
// handle input, this must come after drawing
auto const&io = ImGui::GetIO();
auto const &io = ImGui::GetIO();
auto const mousePos = ox::Vec2{ImGui::GetMousePos()};
if (ImGui::IsItemHovered()) {
auto const wheel = io.MouseWheel;
@@ -556,9 +562,9 @@ void TileSheetEditorImGui::drawPaletteMenu() noexcept {
ImGui::TableSetupColumn("Color16", 0, 3);
ImGui::TableHeadersRow();
{
auto const&pal = m_model.pal();
auto const &pal = m_model.pal();
if (pal.pages.size() > m_model.palettePage()) {
for (auto i = 0u; auto const&c: pal.pages[m_model.palettePage()].colors) {
for (auto i = 0u; auto const &c: pal.pages[m_model.palettePage()].colors) {
ImGui::TableNextRow();
ImGui::PushID(static_cast<int>(i));
// Column: color idx
@@ -580,7 +586,7 @@ void TileSheetEditorImGui::drawPaletteMenu() noexcept {
auto const ic = ImGui::GetColorU32({redf(c), greenf(c), bluef(c), 1});
ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, ic);
ImGui::TableNextColumn();
auto const&name = i < pal.colorNames.size() ? pal.colorNames[i].c_str() : "";
auto const &name = i < pal.colorNames.size() ? pal.colorNames[i].c_str() : "";
ImGui::Text("%s", name);
ImGui::TableNextColumn();
ImGui::Text("(%02d, %02d, %02d)", red16(c), green16(c), blue16(c));
@@ -594,7 +600,7 @@ void TileSheetEditorImGui::drawPaletteMenu() noexcept {
}
ox::Error TileSheetEditorImGui::updateActiveSubsheet(
ox::StringView const&name, int const cols, int const rows) noexcept {
ox::StringViewCR name, int const cols, int const rows) noexcept {
return m_model.updateSubsheet(m_model.activeSubSheetIdx(), name, cols, rows);
}

View File

@@ -82,7 +82,7 @@ class TileSheetEditorImGui: public studio::Editor {
void drawSubsheetSelector(TileSheet::SubSheet&, TileSheet::SubSheetIdx &path);
[[nodiscard]]
static ox::Vec2 clickPos(ImVec2 const&winPos, ox::Vec2 clickPos) noexcept;
static ox::Vec2 clickPos(ImVec2 const &winPos, ox::Vec2 clickPos) noexcept;
protected:
ox::Error saveItem() noexcept override;
@@ -94,11 +94,11 @@ class TileSheetEditorImGui: public studio::Editor {
ox::Error exportSubsheetToPng(int scale) const noexcept;
void drawTileSheet(ox::Vec2 const&fbSize) noexcept;
void drawTileSheet(ox::Vec2 const &fbSize) noexcept;
void drawPaletteMenu() noexcept;
ox::Error updateActiveSubsheet(ox::StringView const&name, int cols, int rows) noexcept;
ox::Error updateActiveSubsheet(ox::StringViewCR name, int cols, int rows) noexcept;
void setActiveSubsheet(TileSheet::SubSheetIdx path) noexcept;

View File

@@ -84,9 +84,9 @@ void TileSheetEditorModel::cut() {
auto const pt1 = m_selection->a;
auto const pt2 = ox::Point{s.columns * TileWidth, s.rows * TileHeight};
turbine::setClipboardObject(m_tctx, std::move(cb));
if (auto const cmd = ox::makeCatch<CutPasteCommand>(
if (auto cmd = ox::make_unique_catch<CutPasteCommand>(
CommandId::Cut, m_img, m_activeSubsSheetIdx, pt1, pt2, blankCb); cmd.ok()) {
std::ignore = pushCommand(cmd.value);
std::ignore = pushCommand(std::move(cmd.value));
}
}
@@ -125,9 +125,9 @@ void TileSheetEditorModel::paste() {
auto const&s = activeSubSheet();
auto const pt1 = m_selection->a;
auto const pt2 = ox::Point{s.columns * TileWidth, s.rows * TileHeight};
if (auto const cmd = ox::makeCatch<CutPasteCommand>(
CommandId::Paste, m_img, m_activeSubsSheetIdx, pt1, pt2, *cb); cmd.ok()) {
std::ignore = pushCommand(cmd.value);
if (auto cmd = ox::make_unique_catch<CutPasteCommand>(
CommandId::Paste, m_img, m_activeSubsSheetIdx, pt1, pt2, *cb); cmd.value) {
std::ignore = pushCommand(std::move(cmd.value));
}
}
@@ -142,7 +142,7 @@ ox::String const &TileSheetEditorModel::palPath() const & noexcept {
ox::Error TileSheetEditorModel::setPalette(ox::StringViewCR path) noexcept {
OX_REQUIRE(uuid, keelCtx(m_tctx).pathToUuid.at(path));
std::ignore = pushCommand(ox::make<PaletteChangeCommand>(
std::ignore = pushCommand(ox::make_unique<PaletteChangeCommand>(
activeSubSheetIdx(), m_img, uuid->toString()));
return {};
}
@@ -169,7 +169,7 @@ void TileSheetEditorModel::drawCommand(ox::Point const &pt, std::size_t const pa
if (m_ongoingDrawCommand) {
m_updated = m_updated || m_ongoingDrawCommand->append(idx);
} else if (activeSubSheet.pixels[idx] != palIdx) {
std::ignore = pushCommand(ox::make<DrawCommand>(
std::ignore = pushCommand(ox::make_unique<DrawCommand>(
m_img, m_activeSubsSheetIdx, idx, static_cast<int>(palIdx)));
}
}
@@ -188,7 +188,7 @@ void TileSheetEditorModel::drawLineCommand(ox::Point const &pt, std::size_t cons
m_ongoingDrawCommand->lineUpdate(m_lineStartPt, pt);
m_updated = true;
} else {
std::ignore = pushCommand(ox::make<DrawCommand>(
std::ignore = pushCommand(ox::make_unique<DrawCommand>(
m_img, m_activeSubsSheetIdx, idx, static_cast<int>(palIdx)));
m_lineStartPt = pt;
}
@@ -198,41 +198,43 @@ void TileSheetEditorModel::endDrawCommand() noexcept {
if (m_ongoingDrawCommand) {
m_ongoingDrawCommand->finish();
m_ongoingDrawCommand = nullptr;
m_lastDrawUpdatePt = {-1, -1};
}
}
void TileSheetEditorModel::addSubsheet(TileSheet::SubSheetIdx const&parentIdx) noexcept {
std::ignore = pushCommand(ox::make<AddSubSheetCommand>(m_img, parentIdx));
void TileSheetEditorModel::addSubsheet(TileSheet::SubSheetIdx const &parentIdx) noexcept {
std::ignore = pushCommand(ox::make_unique<AddSubSheetCommand>(m_img, parentIdx));
}
void TileSheetEditorModel::rmSubsheet(TileSheet::SubSheetIdx const&idx) noexcept {
std::ignore = pushCommand(ox::make<RmSubSheetCommand>(m_img, idx));
void TileSheetEditorModel::rmSubsheet(TileSheet::SubSheetIdx const &idx) noexcept {
std::ignore = pushCommand(ox::make_unique<RmSubSheetCommand>(m_img, idx));
}
void TileSheetEditorModel::insertTiles(
TileSheet::SubSheetIdx const&idx, std::size_t const tileIdx, std::size_t const tileCnt) noexcept {
std::ignore = pushCommand(ox::make<InsertTilesCommand>(m_img, idx, tileIdx, tileCnt));
TileSheet::SubSheetIdx const &idx, std::size_t const tileIdx, std::size_t const tileCnt) noexcept {
std::ignore = pushCommand(ox::make_unique<InsertTilesCommand>(m_img, idx, tileIdx, tileCnt));
}
void TileSheetEditorModel::deleteTiles(
TileSheet::SubSheetIdx const&idx, std::size_t const tileIdx, std::size_t const tileCnt) noexcept {
std::ignore = pushCommand(ox::make<DeleteTilesCommand>(m_img, idx, tileIdx, tileCnt));
TileSheet::SubSheetIdx const &idx, std::size_t const tileIdx, std::size_t const tileCnt) noexcept {
std::ignore = pushCommand(ox::make_unique<DeleteTilesCommand>(m_img, idx, tileIdx, tileCnt));
}
ox::Error TileSheetEditorModel::updateSubsheet(
TileSheet::SubSheetIdx const&idx, ox::StringViewCR name, int const cols, int const rows) noexcept {
OX_REQUIRE(cmd, ox::makeCatch<UpdateSubSheetCommand>(m_img, idx, name, cols, rows));
std::ignore = pushCommand(cmd);
TileSheet::SubSheetIdx const &idx, ox::StringViewCR name, int const cols, int const rows) noexcept {
OX_REQUIRE_M(cmd, ox::make_unique_catch<UpdateSubSheetCommand>(m_img, idx, name, cols, rows));
std::ignore = pushCommand(std::move(cmd));
return {};
}
void TileSheetEditorModel::setActiveSubsheet(TileSheet::SubSheetIdx const&idx) noexcept {
void TileSheetEditorModel::setActiveSubsheet(TileSheet::SubSheetIdx const &idx) noexcept {
m_activeSubsSheetIdx = idx;
this->activeSubsheetChanged.emit(m_activeSubsSheetIdx);
clearSelection();
m_lastDrawUpdatePt = {-1, -1};
}
void TileSheetEditorModel::fill(ox::Point const&pt, int const palIdx) noexcept {
void TileSheetEditorModel::fill(ox::Point const &pt, uint8_t const palIdx) noexcept {
auto const&activeSubSheet = getSubSheet(m_img, m_activeSubsSheetIdx);
// build idx list
if (pt.x >= activeSubSheet.columns * TileWidth || pt.y >= activeSubSheet.rows * TileHeight) {
@@ -253,33 +255,33 @@ void TileSheetEditorModel::fill(ox::Point const&pt, int const palIdx) noexcept {
if (m_ongoingDrawCommand) {
m_updated = m_updated || m_ongoingDrawCommand->append(idxList);
} else if (getPixel(activeSubSheet, pt) != palIdx) {
std::ignore = pushCommand(ox::make<DrawCommand>(m_img, m_activeSubsSheetIdx, idxList, palIdx));
std::ignore = pushCommand(ox::make_unique<DrawCommand>(m_img, m_activeSubsSheetIdx, idxList, palIdx));
}
}
ox::Error TileSheetEditorModel::rotateLeft() noexcept {
auto &ss = activeSubSheet();
auto const &ss = activeSubSheet();
ox::Point pt1, pt2{ss.columns * TileWidth - 1, ss.rows * TileHeight - 1};
if (m_selection) {
pt1 = m_selection->a;
pt2 = m_selection->b;
}
return pushCommand(ox::make<RotateCommand>(
return pushCommand(ox::make_unique<RotateCommand>(
m_img, m_activeSubsSheetIdx, pt1, pt2, RotateCommand::Direction::Left));
}
ox::Error TileSheetEditorModel::rotateRight() noexcept {
auto &ss = activeSubSheet();
auto const &ss = activeSubSheet();
ox::Point pt1, pt2{ss.columns * TileWidth - 1, ss.rows * TileHeight - 1};
if (m_selection) {
pt1 = m_selection->a;
pt2 = m_selection->b;
}
return pushCommand(ox::make<RotateCommand>(
return pushCommand(ox::make_unique<RotateCommand>(
m_img, m_activeSubsSheetIdx, pt1, pt2, RotateCommand::Direction::Right));
}
void TileSheetEditorModel::setSelection(studio::Selection const&sel) noexcept {
void TileSheetEditorModel::setSelection(studio::Selection const &sel) noexcept {
auto const &ss = activeSubSheet();
if (sel.a.x < ss.columns * TileWidth && sel.a.y < ss.rows * TileHeight) {
m_selection.emplace(sel);
@@ -290,7 +292,7 @@ void TileSheetEditorModel::setSelection(studio::Selection const&sel) noexcept {
m_updated = true;
}
void TileSheetEditorModel::select(ox::Point const&pt) noexcept {
void TileSheetEditorModel::select(ox::Point const &pt) noexcept {
if (m_selTracker.updateCursorPoint(pt)) {
setSelection(m_selTracker.selection());
}
@@ -313,11 +315,15 @@ void TileSheetEditorModel::clearSelection() noexcept {
m_selection.reset();
}
bool TileSheetEditorModel::hasSelection() const noexcept {
return m_selection.has_value();
}
bool TileSheetEditorModel::updated() const noexcept {
return m_updated;
}
ox::Error TileSheetEditorModel::markUpdatedCmdId(studio::UndoCommand const*cmd) noexcept {
ox::Error TileSheetEditorModel::markUpdatedCmdId(studio::UndoCommand const *cmd) noexcept {
m_updated = true;
auto const cmdId = cmd->commandId();
if (static_cast<CommandId>(cmdId) == CommandId::PaletteChange) {
@@ -363,7 +369,7 @@ ox::Error TileSheetEditorModel::flipX() noexcept {
a = m_selection->a;
b = m_selection->b;
}
return pushCommand(ox::make<FlipXCommand>(m_img, m_activeSubsSheetIdx, a, b));
return pushCommand(ox::make_unique<FlipXCommand>(m_img, m_activeSubsSheetIdx, a, b));
}
ox::Error TileSheetEditorModel::flipY() noexcept {
@@ -374,7 +380,7 @@ ox::Error TileSheetEditorModel::flipY() noexcept {
a = m_selection->a;
b = m_selection->b;
}
return pushCommand(ox::make<FlipYCommand>(m_img, m_activeSubsSheetIdx, a, b));
return pushCommand(ox::make_unique<FlipYCommand>(m_img, m_activeSubsSheetIdx, a, b));
}
bool TileSheetEditorModel::rotateEligible() const noexcept {
@@ -388,14 +394,14 @@ bool TileSheetEditorModel::rotateEligible() const noexcept {
}
ox::Error TileSheetEditorModel::moveSubSheet(TileSheet::SubSheetIdx src, TileSheet::SubSheetIdx dst) noexcept {
return pushCommand(ox::make<MoveSubSheetCommand>(m_img, std::move(src), std::move(dst)));
return pushCommand(ox::make_unique<MoveSubSheetCommand>(m_img, std::move(src), std::move(dst)));
}
void TileSheetEditorModel::getFillPixels(
TileSheet::SubSheet const&activeSubSheet,
ox::Span<bool> pixels,
ox::Point const&pt,
int const oldColor) const noexcept {
ox::Span<bool> const pixels,
ox::Point const &pt,
uint8_t const oldColor) noexcept {
auto const idx = ptToIdx(pt, activeSubSheet.columns);
auto const relIdx = idx % PixelsPerTile;
if (pixels[relIdx] || activeSubSheet.pixels[idx] != oldColor) {
@@ -435,9 +441,9 @@ void TileSheetEditorModel::setPalPath() noexcept {
}
}
ox::Error TileSheetEditorModel::pushCommand(studio::UndoCommand *cmd) noexcept {
std::ignore = m_undoStack.push(ox::UPtr{cmd});
m_ongoingDrawCommand = dynamic_cast<DrawCommand*>(cmd);
ox::Error TileSheetEditorModel::pushCommand(ox::UPtr<studio::UndoCommand> &&cmd) noexcept {
m_ongoingDrawCommand = dynamic_cast<DrawCommand*>(cmd.get());
std::ignore = m_undoStack.push(std::move(cmd));
m_updated = true;
return {};
}

View File

@@ -14,7 +14,7 @@
namespace nostalgia::gfx {
class TileSheetEditorModel: public ox::SignalHandler {
class TileSheetEditorModel final: public ox::SignalHandler {
public:
ox::Signal<ox::Error(const TileSheet::SubSheetIdx&)> activeSubsheetChanged;
@@ -35,7 +35,7 @@ class TileSheetEditorModel: public ox::SignalHandler {
studio::SelectionTracker m_selTracker;
ox::Optional<studio::Selection> m_selection;
ox::Point m_lineStartPt;
ox::Point m_lastDrawUpdatePt;
ox::Point m_lastDrawUpdatePt{-1, -1};
bool m_updated = false;
public:
@@ -53,13 +53,13 @@ class TileSheetEditorModel: public ox::SignalHandler {
bool acceptsClipboardPayload() const noexcept;
[[nodiscard]]
constexpr TileSheet const&img() const noexcept;
constexpr TileSheet const &img() const noexcept;
[[nodiscard]]
constexpr TileSheet &img() noexcept;
[[nodiscard]]
constexpr Palette const&pal() const noexcept;
constexpr Palette const &pal() const noexcept;
[[nodiscard]]
ox::String const &palPath() const & noexcept;
@@ -71,26 +71,26 @@ class TileSheetEditorModel: public ox::SignalHandler {
[[nodiscard]]
size_t palettePage() const noexcept;
void drawCommand(ox::Point const&pt, std::size_t palIdx) noexcept;
void drawCommand(ox::Point const &pt, std::size_t palIdx) noexcept;
void drawLineCommand(ox::Point const&pt, std::size_t palIdx) noexcept;
void drawLineCommand(ox::Point const &pt, std::size_t palIdx) noexcept;
void endDrawCommand() noexcept;
void addSubsheet(TileSheet::SubSheetIdx const&parentIdx) noexcept;
void addSubsheet(TileSheet::SubSheetIdx const &parentIdx) noexcept;
void rmSubsheet(TileSheet::SubSheetIdx const&idx) noexcept;
void rmSubsheet(TileSheet::SubSheetIdx const &idx) noexcept;
void insertTiles(TileSheet::SubSheetIdx const&idx, std::size_t tileIdx, std::size_t tileCnt) noexcept;
void insertTiles(TileSheet::SubSheetIdx const &idx, std::size_t tileIdx, std::size_t tileCnt) noexcept;
void deleteTiles(TileSheet::SubSheetIdx const&idx, std::size_t tileIdx, std::size_t tileCnt) noexcept;
void deleteTiles(TileSheet::SubSheetIdx const &idx, std::size_t tileIdx, std::size_t tileCnt) noexcept;
ox::Error updateSubsheet(TileSheet::SubSheetIdx const&idx, ox::StringView const&name, int cols, int rows) noexcept;
ox::Error updateSubsheet(TileSheet::SubSheetIdx const &idx, ox::StringView const &name, int cols, int rows) noexcept;
void setActiveSubsheet(TileSheet::SubSheetIdx const&) noexcept;
void setActiveSubsheet(TileSheet::SubSheetIdx const &) noexcept;
[[nodiscard]]
TileSheet::SubSheet const&activeSubSheet() const noexcept {
TileSheet::SubSheet const &activeSubSheet() const noexcept {
return getSubSheet(m_img, m_activeSubsSheetIdx);
}
@@ -100,28 +100,31 @@ class TileSheetEditorModel: public ox::SignalHandler {
}
[[nodiscard]]
constexpr TileSheet::SubSheetIdx const&activeSubSheetIdx() const noexcept {
constexpr TileSheet::SubSheetIdx const &activeSubSheetIdx() const noexcept {
return m_activeSubsSheetIdx;
}
void fill(ox::Point const&pt, int palIdx) noexcept;
void fill(ox::Point const &pt, uint8_t palIdx) noexcept;
ox::Error rotateLeft() noexcept;
ox::Error rotateRight() noexcept;
void setSelection(studio::Selection const&sel) noexcept;
void setSelection(studio::Selection const &sel) noexcept;
void select(ox::Point const&pt) noexcept;
void select(ox::Point const &pt) noexcept;
void completeSelection() noexcept;
void clearSelection() noexcept;
[[nodiscard]]
bool hasSelection() const noexcept;
[[nodiscard]]
bool updated() const noexcept;
ox::Error markUpdatedCmdId(studio::UndoCommand const*cmd) noexcept;
ox::Error markUpdatedCmdId(studio::UndoCommand const *cmd) noexcept;
ox::Error markUpdated() noexcept;
@@ -144,21 +147,21 @@ class TileSheetEditorModel: public ox::SignalHandler {
ox::Error moveSubSheet(TileSheet::SubSheetIdx src, TileSheet::SubSheetIdx dst) noexcept;
private:
void getFillPixels(
TileSheet::SubSheet const&activeSubSheet,
static void getFillPixels(
TileSheet::SubSheet const &activeSubSheet,
ox::Span<bool> pixels,
ox::Point const&pt,
int oldColor) const noexcept;
ox::Point const &pt,
uint8_t oldColor) noexcept;
void setPalPath() noexcept;
ox::Error pushCommand(studio::UndoCommand *cmd) noexcept;
ox::Error pushCommand(ox::UPtr<studio::UndoCommand> &&cmd) noexcept;
ox::Error handleFileRename(ox::StringViewCR, ox::StringViewCR newPath, ox::UUID const&id) noexcept;
ox::Error handleFileRename(ox::StringViewCR, ox::StringViewCR newPath, ox::UUID const &id) noexcept;
};
constexpr TileSheet const&TileSheetEditorModel::img() const noexcept {
constexpr TileSheet const &TileSheetEditorModel::img() const noexcept {
return m_img;
}
@@ -166,7 +169,7 @@ constexpr TileSheet &TileSheetEditorModel::img() noexcept {
return m_img;
}
constexpr Palette const&TileSheetEditorModel::pal() const noexcept {
constexpr Palette const &TileSheetEditorModel::pal() const noexcept {
if (m_pal) {
return *m_pal;
}

View File

@@ -89,7 +89,7 @@ void TileSheetEditorView::clickSelect(ox::Vec2 const&paneSize, ox::Vec2 const&cl
void TileSheetEditorView::clickFill(ox::Vec2 const&paneSize, ox::Vec2 const&clickPos) noexcept {
auto const pt = clickPoint(paneSize, clickPos);
m_model.fill(pt, static_cast<int>(m_palIdx));
m_model.fill(pt, static_cast<uint8_t>(m_palIdx));
}
void TileSheetEditorView::releaseMouseButton(TileSheetTool tool) noexcept {

View File

@@ -15,7 +15,7 @@ target_link_libraries(
target_compile_definitions(
NostalgiaStudio PUBLIC
OLYMPIC_APP_VERSION="dev build"
OLYMPIC_APP_VERSION="d2025.06.0"
)
install(

View File

@@ -18,7 +18,7 @@
<string>APPL</string>
<key>CFBundleVersion</key>
<string>dev build</string>
<string>d2025.06.0</string>
<key>LSMinimumSystemVersion</key>
<string>12.0.0</string>

View File

@@ -154,7 +154,7 @@ class AssetRef: public ox::SignalHandler {
private:
constexpr ox::Error emitUpdated() const noexcept {
updated.emit();
return ox::Error(0);
return {};
}
};

View File

@@ -60,6 +60,11 @@ ox::Result<ox::CStringView> uuidToPath(Context &ctx, ox::StringViewCR uuid) noex
ox::Result<ox::CStringView> uuidToPath(Context &ctx, ox::UUID const&uuid) noexcept;
[[nodiscard]]
constexpr bool isUuidUrl(ox::StringViewCR path) noexcept {
return ox::beginsWith(path, "uuid://");
}
#ifndef OX_BARE_METAL
namespace detail {

View File

@@ -260,7 +260,7 @@ ox::Result<DstType> convertObjToObj(
template<typename DstType>
ox::Result<DstType> convert(Context &ctx, ox::BufferView const&src) noexcept {
OX_REQUIRE(out, convert(ctx, src, ox::ModelTypeName_v<DstType>, ox::ModelTypeVersion_v<DstType>));
return std::move(wrapCast<DstType>(out));
return std::move(wrapCast<DstType>(*out));
}
template<typename DstType>

View File

@@ -10,6 +10,6 @@
namespace studio {
void registerModule(studio::Module const*) noexcept;
void registerModule(Module const*) noexcept;
}

View File

@@ -1,17 +1,17 @@
add_library(
StudioAppLib
aboutpopup.cpp
app.cpp
clawviewer.cpp
deleteconfirmation.cpp
filedialogmanager.cpp
font.cpp
makecopypopup.cpp
newdir.cpp
newmenu.cpp
newproject.cpp
popups/about.cpp
popups/deleteconfirmation.cpp
popups/makecopy.cpp
popups/newdir.cpp
popups/newmenu.cpp
popups/newproject.cpp
popups/renamefile.cpp
projectexplorer.cpp
renamefile.cpp
studioui.cpp
)
target_compile_definitions(

View File

@@ -28,10 +28,15 @@ class StudioUIDrawer: public turbine::gl::Drawer {
};
static void keyEventHandler(turbine::Context &ctx, turbine::Key key, bool down) noexcept {
auto sctx = turbine::applicationData<studio::Context>(ctx);
auto const sctx = turbine::applicationData<studio::Context>(ctx);
sctx->ui.handleKeyEvent(key, down);
}
static void mouseButtonEventHandler(turbine::Context &ctx, int const btn, bool const down) noexcept {
auto const sctx = turbine::applicationData<studio::Context>(ctx);
sctx->ui.handleMouseButtonEvent(btn, down);
}
[[nodiscard]]
ox::Vector<ox::SpanView<uint8_t>> WindowIcons() noexcept;
@@ -43,7 +48,8 @@ static ox::Error runApp(
oxLogError(turbine::setWindowIcon(*ctx, WindowIcons()));
turbine::setWindowTitle(*ctx, keelCtx(*ctx).appName);
turbine::setKeyEventHandler(*ctx, keyEventHandler);
turbine::setRefreshWithin(*ctx, 0);
turbine::setMouseButtonEventHandler(*ctx, mouseButtonEventHandler);
turbine::requireRefreshWithin(*ctx, 0);
StudioUI ui(*ctx, projectDataDir);
StudioUIDrawer drawer(ui);
turbine::gl::addDrawer(*ctx, &drawer);
@@ -71,9 +77,9 @@ static ox::Error run(
}
ox::Error run(
ox::StringView project,
ox::StringView appName,
ox::StringView projectDataDir,
ox::StringView const project,
ox::StringView const appName,
ox::StringView const projectDataDir,
ox::SpanView<ox::CString> args) noexcept {
return studio::run(ox::sfmt("{} {}", project, appName), projectDataDir, args);
}

View File

@@ -5,7 +5,7 @@
#include <imgui.h>
#include <studio/imguiutil.hpp>
#include "aboutpopup.hpp"
#include "about.hpp"
namespace olympic {
extern ox::String appVersion;
@@ -15,7 +15,11 @@ namespace studio {
AboutPopup::AboutPopup(turbine::Context &ctx) noexcept:
Popup("About"),
#ifdef DEBUG
m_text{sfmt("{} [DEBUG] - {}", keelCtx(ctx).appName, olympic::appVersion)} {
#else
m_text{sfmt("{} - {}", keelCtx(ctx).appName, olympic::appVersion)} {
#endif
}
void AboutPopup::draw(Context &sctx) noexcept {

View File

@@ -2,6 +2,8 @@
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <turbine/gfx.hpp>
#include <studio/imguiutil.hpp>
#include "deleteconfirmation.hpp"
@@ -43,6 +45,9 @@ void DeleteConfirmation::draw(Context &ctx) noexcept {
ImGui::OpenPopup(title().c_str());
m_stage = Stage::Open;
m_open = true;
// require extended refresh in case new contents require resize
// this can take around a second
turbine::requireRefreshFor(ctx.tctx, 1000);
[[fallthrough]];
case Stage::Open:
drawWindow(ctx.tctx, m_open, [this] {

View File

@@ -4,7 +4,7 @@
#include <studio/imguiutil.hpp>
#include "makecopypopup.hpp"
#include "makecopy.hpp"
namespace studio {

View File

@@ -80,9 +80,9 @@ void NewMenu::addItemMaker(ox::UPtr<ItemMaker> &&im) noexcept {
});
}
void NewMenu::installItemTemplate(ox::UPtr<ItemTemplate> &tmplt) noexcept {
void NewMenu::installItemTemplate(ox::UPtr<ItemTemplate> &&tmplt) noexcept {
for (auto const&im : m_types) {
if (im->installTemplate(tmplt)) {
if (im->installTemplate(std::move(tmplt))) {
break;
}
}

View File

@@ -72,7 +72,7 @@ class NewMenu final: public Popup {
void addItemMaker(ox::UPtr<ItemMaker> &&im) noexcept;
void installItemTemplate(ox::UPtr<ItemTemplate> &tmplt) noexcept;
void installItemTemplate(ox::UPtr<ItemTemplate> &&tmplt) noexcept;
private:
void drawNewItemType(Context const&sctx) noexcept;

View File

@@ -7,7 +7,7 @@
#include <studio/imguiutil.hpp>
#include <utility>
#include "filedialogmanager.hpp"
#include "../filedialogmanager.hpp"
#include "newproject.hpp"
namespace studio {

View File

@@ -7,6 +7,8 @@
#include <imgui.h>
#include <ox/std/def.hpp>
#include <keel/media.hpp>
#include <glutils/glutils.hpp>
#include <turbine/turbine.hpp>
@@ -17,6 +19,8 @@
#include "font.hpp"
#include "studioui.hpp"
#include <complex>
#ifdef OX_OS_Darwin
#define STUDIO_CTRL "Cmd"
#else
@@ -30,18 +34,6 @@ static bool shutdownHandler(turbine::Context &ctx) {
return sctx->ui.handleShutdown();
}
void navigateTo(Context &ctx, ox::StringParam filePath, ox::StringParam navArgs) noexcept {
ox::String path = std::move(filePath);
if (beginsWith(path, "uuid://")) {
auto [p, err] = keel::uuidUrlToPath(keelCtx(ctx), path);
if (err) {
return;
}
path = p;
}
ctx.ui.navigateTo(std::move(path), std::move(navArgs));
}
namespace ig {
extern bool s_mainWinHasFocus;
}
@@ -54,7 +46,7 @@ void registerModule(Module const*mod) noexcept {
}
struct StudioConfig {
struct StudioConfigV1 {
static constexpr auto TypeName = "net.drinkingtea.studio.StudioConfig";
static constexpr auto TypeVersion = 1;
ox::String projectPath;
@@ -63,16 +55,67 @@ struct StudioConfig {
bool showProjectExplorer = true;
};
OX_MODEL_BEGIN(StudioConfig)
OX_MODEL_BEGIN(StudioConfigV1)
OX_MODEL_FIELD_RENAME(activeTabItemName, active_tab_item_name)
OX_MODEL_FIELD_RENAME(projectPath, project_path)
OX_MODEL_FIELD_RENAME(openFiles, open_files)
OX_MODEL_FIELD_RENAME(showProjectExplorer, show_project_explorer)
OX_MODEL_END()
StudioUI::StudioUI(turbine::Context &ctx, ox::StringParam projectDataDir) noexcept:
m_sctx{*this, ctx},
m_tctx{ctx},
struct StudioConfigV2 {
static constexpr auto TypeName = "net.drinkingtea.studio.StudioConfig";
static constexpr auto TypeVersion = 2;
struct ProjectConfig {
static constexpr auto TypeName = "net.drinkingtea.studio.ProjectConfig";
static constexpr auto TypeVersion = 2;
ox::String projectPath;
ox::String activeTabItemName;
ox::Vector<ox::String> openFiles;
};
ox::Vector<ProjectConfig> projects;
bool showProjectExplorer = true;
[[nodiscard]]
constexpr ProjectConfig const *project() const {
return projects.empty() ? nullptr : &projects[0];
}
[[nodiscard]]
constexpr ProjectConfig *project() {
return projects.empty() ? nullptr : &projects[0];
}
};
OX_MODEL_BEGIN(StudioConfigV2::ProjectConfig)
OX_MODEL_FIELD_RENAME(activeTabItemName, active_tab_item_name)
OX_MODEL_FIELD_RENAME(projectPath, project_path)
OX_MODEL_FIELD_RENAME(openFiles, open_files)
OX_MODEL_END()
OX_MODEL_BEGIN(StudioConfigV2)
OX_MODEL_FIELD(projects)
OX_MODEL_FIELD_RENAME(showProjectExplorer, show_project_explorer)
OX_MODEL_END()
static ox::Error convertStudioConfigV1ToStudioConfigV2(
keel::Context&,
StudioConfigV1 &src,
StudioConfigV2 &dst) noexcept {
dst.projects.emplace_back(StudioConfigV2::ProjectConfig{
.projectPath = std::move(src.projectPath),
.activeTabItemName = std::move(src.activeTabItemName),
.openFiles = std::move(src.openFiles),
});
dst.showProjectExplorer = src.showProjectExplorer;
return {};
}
using StudioConfig = StudioConfigV2;
StudioUI::StudioUI(turbine::Context &tctx, ox::StringParam projectDataDir) noexcept:
m_sctx{*this, tctx},
m_projectDataDir{std::move(projectDataDir)} {
{
ImFontConfig fontCfg;
@@ -83,12 +126,18 @@ StudioUI::StudioUI(turbine::Context &ctx, ox::StringParam projectDataDir) noexce
// but AddFontFromMemoryTTF requires a mutable buffer.
// However, setting fontCfg.FontDataOwnedByAtlas ensures
// that it will still be treated as const.
// ImGui documentation recognize that this is a bad design,
// ImGui documentation recognizes that this is a bad design,
// and hopefully it will change at some point.
io.Fonts->AddFontFromMemoryTTF(const_cast<uint8_t*>(font.data()), static_cast<int>(font.size()), 13, &fontCfg);
}
auto &kctx = keelCtx(m_tctx);
kctx.converters.emplace_back(keel::Converter::make<convertStudioConfigV1ToStudioConfigV2>());
oxLogError(headerizeConfigFile<StudioConfigV1>(kctx));
turbine::setApplicationData(m_tctx, &m_sctx);
turbine::setShutdownHandler(m_tctx, shutdownHandler);
m_sctx.navCallback = [this](ox::StringParam filePath, ox::StringParam navArgs) {
handleNavigationChange(std::move(filePath), std::move(navArgs));
};
m_projectExplorer.fileChosen.connect(this, &StudioUI::openFile);
m_projectExplorer.addDir.connect(this, &StudioUI::addDir);
m_projectExplorer.addItem.connect(this, &StudioUI::addFile);
@@ -102,21 +151,14 @@ StudioUI::StudioUI(turbine::Context &ctx, ox::StringParam projectDataDir) noexce
m_newMenu.finished.connect(this, &StudioUI::openFile);
m_closeAppConfirm.response.connect(this, &StudioUI::handleCloseAppResponse);
m_closeFileConfirm.response.connect(this, &StudioUI::handleCloseFileResponse);
m_removeRecentProject.dlg.response.connect(this, &StudioUI::handleRemoveRecentProjectResponse);
loadModules();
// open project and files
auto const [config, err] = studio::readConfig<StudioConfig>(keelCtx(m_tctx));
m_showProjectExplorer = config.showProjectExplorer;
if (!err) {
auto const openProjErr = openProjectPath(config.projectPath);
if (!openProjErr) {
for (auto const&f: config.openFiles) {
auto const openFileErr = openFileActiveTab(f, config.activeTabItemName == f);
if (openFileErr) {
oxErrorf("\nCould not open editor for file:\n\t{}\nReason:\n\t{}\n", f, toStr(openFileErr));
continue;
}
m_activeEditor = m_editors.back().value->get();
}
if (auto const pc = config.project()) {
oxLogError(openProjectPath(pc->projectPath));
}
} else {
if constexpr(!ox::defines::Debug) {
@@ -135,14 +177,24 @@ void StudioUI::handleKeyEvent(turbine::Key const key, bool const down) noexcept
}
}
void StudioUI::navigateTo(ox::StringParam path, ox::StringParam navArgs) noexcept {
void StudioUI::handleMouseButtonEvent(int const btn, bool const down) noexcept {
if (down) {
if (btn == 3) { // back button
navigateBack(m_sctx);
} else if (btn == 4) { // forward button
navigateForward(m_sctx);
}
}
}
void StudioUI::handleNavigationChange(ox::StringParam path, ox::StringParam navArgs) noexcept {
m_navAction.emplace(std::move(path), std::move(navArgs));
}
void StudioUI::draw() noexcept {
glutils::clearScreen();
drawMenu();
auto const&viewport = *ImGui::GetMainViewport();
auto const &viewport = *ImGui::GetMainViewport();
ImGui::SetNextWindowPos(viewport.WorkPos);
ImGui::SetNextWindowSize(viewport.WorkSize);
ImGui::SetNextWindowViewport(viewport.ID);
@@ -194,6 +246,18 @@ void StudioUI::drawMenu() noexcept {
if (ImGui::MenuItem("Open Project...", STUDIO_CTRL "+O")) {
m_taskRunner.add(*ox::make<FileDialogManager>(this, &StudioUI::openProjectPath));
}
if (ImGui::BeginMenu("Recent Projects", m_recentProjects.size() > 1)) {
for (size_t i = 1; i < m_recentProjects.size(); ++i) {
auto const &p = m_recentProjects[i];
if (ImGui::MenuItem(p.c_str())) {
if (openProjectPath(p)) {
m_removeRecentProject.idx = i;
m_removeRecentProject.dlg.open();
}
}
}
ImGui::EndMenu();
}
if (ImGui::MenuItem(
"Save",
STUDIO_CTRL "+S",
@@ -207,7 +271,7 @@ void StudioUI::drawMenu() noexcept {
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Edit")) {
auto undoStack = m_activeEditor ? m_activeEditor->undoStack() : nullptr;
auto const undoStack = m_activeEditor ? m_activeEditor->undoStack() : nullptr;
if (ImGui::MenuItem(
"Undo", STUDIO_CTRL "+Z", false, undoStack && undoStack->canUndo())) {
oxLogError(undoStack->undo());
@@ -247,6 +311,19 @@ void StudioUI::drawMenu() noexcept {
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Navigate")) {
constexpr auto backShortcut = ox::defines::OS == ox::OS::Darwin ? "Cmd+[" : "Alt+Left Arrow";
constexpr auto fwdShortcut = ox::defines::OS == ox::OS::Darwin ? "Cmd+]" : "Alt+Right Arrow";
if (ImGui::MenuItem("Back", backShortcut, false, m_sctx.navIdx > 1)) {
navigateBack(m_sctx);
}
if (ImGui::MenuItem(
"Forward", fwdShortcut, false,
m_sctx.navIdx < m_sctx.navStack.size())) {
navigateForward(m_sctx);
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Help")) {
if (ImGui::MenuItem("About")) {
m_aboutPopup.open();
@@ -270,7 +347,7 @@ void StudioUI::drawTabBar() noexcept {
void StudioUI::drawTabs() noexcept {
for (auto it = m_editors.begin(); it != m_editors.end();) {
auto const&e = *it;
auto const &e = *it;
auto open = true;
auto const unsavedChanges = e->unsavedChanges() ? ImGuiTabItemFlags_UnsavedDocument : 0;
auto const selected = m_activeEditorUpdatePending == e.get() ? ImGuiTabItemFlags_SetSelected : 0;
@@ -279,19 +356,27 @@ void StudioUI::drawTabs() noexcept {
if (m_activeEditor != e.get()) [[unlikely]] {
m_activeEditor = e.get();
studio::editConfig<StudioConfig>(keelCtx(m_tctx), [&](StudioConfig &config) {
config.activeTabItemName = m_activeEditor->itemPath();
if (auto const pc = config.project()) {
pc->activeTabItemName = m_activeEditor->itemPath();
}
});
turbine::setRefreshWithin(m_tctx, 0);
turbine::requireRefreshWithin(m_tctx, 0);
} else [[likely]] {
if (m_activeEditorUpdatePending == e.get()) [[unlikely]] {
m_activeEditorUpdatePending = nullptr;
}
if (m_activeEditorOnLastDraw != e.get()) [[unlikely]] {
m_activeEditor->onActivated();
if (!m_sctx.navIdx ||
(m_sctx.navIdx <= m_sctx.navStack.size() &&
m_sctx.navStack[m_sctx.navIdx - 1].filePath != m_activeEditor->itemPath())) {
m_sctx.navStack.resize(m_sctx.navIdx);
++m_sctx.navIdx;
m_sctx.navStack.emplace_back(ox::String{m_activeEditor->itemPath()}, ox::String{""});
}
}
if (m_closeActiveTab) [[unlikely]] {
ImGui::SetTabItemClosed(e->itemDisplayName().c_str());
} else if (open) [[likely]] {
e->draw(m_sctx);
}
@@ -331,20 +416,21 @@ void StudioUI::drawTabs() noexcept {
m_closeActiveTab = false;
}
if (m_navAction) {
oxLogError(openFile(m_navAction->path));
m_activeEditor->navigateTo(m_navAction->args);
m_navAction.reset();
if (!openFile(m_navAction->path)) {
m_activeEditor->navigateTo(m_navAction->args);
m_navAction.reset();
}
}
}
void StudioUI::loadEditorMaker(EditorMaker const&editorMaker) noexcept {
for (auto const&ext : editorMaker.fileTypes) {
void StudioUI::loadEditorMaker(EditorMaker const &editorMaker) noexcept {
for (auto const &ext : editorMaker.fileTypes) {
m_editorMakers[ext] = editorMaker.make;
}
}
void StudioUI::loadModule(Module const&mod) noexcept {
for (auto const&editorMaker : mod.editors(m_sctx)) {
void StudioUI::loadModule(Module const &mod) noexcept {
for (auto const &editorMaker : mod.editors(m_sctx)) {
loadEditorMaker(editorMaker);
}
for (auto &im : mod.itemMakers(m_sctx)) {
@@ -352,7 +438,7 @@ void StudioUI::loadModule(Module const&mod) noexcept {
}
auto tmplts = mod.itemTemplates(m_sctx);
for (auto &t : tmplts) {
m_newMenu.installItemTemplate(t);
m_newMenu.installItemTemplate(std::move(t));
}
}
@@ -442,6 +528,22 @@ void StudioUI::handleKeyInput() noexcept {
}
}
}
if constexpr (ox::defines::OS == ox::OS::Darwin) {
if (ImGui::IsKeyPressed(ImGuiKey_LeftBracket)) {
navigateBack(m_sctx);
} else if (ImGui::IsKeyPressed(ImGuiKey_RightBracket)) {
navigateForward(m_sctx);
}
}
}
if constexpr (ox::defines::OS != ox::OS::Darwin) {
if (ImGui::IsKeyDown(ImGuiKey_ModAlt)) {
if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow)) {
navigateBack(m_sctx);
} else if (ImGui::IsKeyPressed(ImGuiKey_RightArrow)) {
navigateForward(m_sctx);
}
}
}
}
@@ -464,14 +566,16 @@ ox::Error StudioUI::renameFile(ox::StringViewCR path) noexcept {
return m_renameFile.openPath(path);
}
ox::Error StudioUI::handleMoveFile(ox::StringViewCR oldPath, ox::StringViewCR newPath, ox::UUID const&) noexcept {
ox::Error StudioUI::handleMoveFile(ox::StringViewCR oldPath, ox::StringViewCR newPath, ox::UUID const &) noexcept {
for (auto &f : m_openFiles) {
if (f == oldPath) {
f = newPath;
editConfig<StudioConfig>(keelCtx(m_sctx), [&](StudioConfig &cfg) {
auto p = find(cfg.openFiles.begin(), cfg.openFiles.end(), oldPath);
*p = newPath;
cfg.activeTabItemName = newPath;
if (auto const pc = cfg.project()) {
auto p = find(pc->openFiles.begin(), pc->openFiles.end(), oldPath);
*p = newPath;
pc->activeTabItemName = newPath;
}
});
break;
}
@@ -485,24 +589,36 @@ ox::Error StudioUI::handleMoveFile(ox::StringViewCR oldPath, ox::StringViewCR ne
ox::Error StudioUI::handleDeleteDir(ox::StringViewCR path) noexcept {
auto const p = sfmt("{}/", path);
for (auto &e : m_editors) {
if (beginsWith(e->itemPath(), p)) {
oxLogError(closeFile(path));
m_closeActiveTab = true;
break;
}
}
std::ignore = m_editors.erase(
std::remove_if(
m_editors.begin(), m_editors.end(),
[&](ox::UPtr<BaseEditor> const &e) {
if (beginsWith(e->itemPath(), p)) {
oxLogError(closeFile(path));
if (e.get() != m_activeEditor) {
return true;
}
m_closeActiveTab = true;
}
return false;
}));
return m_projectExplorer.refreshProjectTreeModel();
}
ox::Error StudioUI::handleDeleteFile(ox::StringViewCR path) noexcept {
for (auto &e : m_editors) {
if (path == e->itemPath()) {
oxLogError(closeFile(path));
m_closeActiveTab = true;
break;
}
}
std::ignore = m_editors.erase(
std::remove_if(
m_editors.begin(), m_editors.end(),
[&](ox::UPtr<BaseEditor> const &e) {
if (path == e->itemPath()) {
oxLogError(closeFile(path));
if (e.get() != m_activeEditor) {
return true;
}
m_closeActiveTab = true;
}
return false;
}));
return m_projectExplorer.refreshProjectTreeModel();
}
@@ -533,6 +649,9 @@ ox::Error StudioUI::openProjectPath(ox::StringParam path) noexcept {
ox::make_unique_catch<Project>(keelCtx(m_tctx), std::move(path), m_projectDataDir)
.moveTo(m_project));
m_sctx.project = m_project.get();
m_activeEditor = nullptr;
m_activeEditorOnLastDraw = nullptr;
m_activeEditorUpdatePending = nullptr;
turbine::setWindowTitle(
m_tctx, ox::sfmt("{} - {}", keelCtx(m_tctx).appName, m_project->projectPath()));
m_deleteConfirmation.deleteFile.connect(m_sctx.project, &Project::deleteItem);
@@ -546,8 +665,36 @@ ox::Error StudioUI::openProjectPath(ox::StringParam path) noexcept {
m_openFiles.clear();
m_editors.clear();
studio::editConfig<StudioConfig>(keelCtx(m_tctx), [&](StudioConfig &config) {
config.projectPath = ox::String(m_project->projectPath());
config.openFiles.clear();
auto const pcIt = std::find_if(
config.projects.begin(), config.projects.end(),
[this](StudioConfig::ProjectConfig const &pc) {
return pc.projectPath == m_project->projectPath();
});
if (pcIt != config.projects.end()) {
auto p = std::move(*pcIt);
std::ignore = config.projects.erase(pcIt);
auto &pc = *config.projects.emplace(0, std::move(p));
for (auto const &f: pc.openFiles) {
auto const openFileErr = openFileActiveTab(f, pc.activeTabItemName == f);
if (openFileErr) {
oxErrorf("\nCould not open editor for file:\n\t{}\nReason:\n\t{}\n", f, toStr(openFileErr));
continue;
}
m_activeEditor = m_editors.back().value->get();
}
} else {
config.projects.emplace(0, StudioConfig::ProjectConfig{
.projectPath = ox::String{m_project->projectPath()},
.activeTabItemName = {},
.openFiles = {},
});
}
config.projects.resize(ox::min<size_t>(10, config.projects.size()));
m_recentProjects.clear();
m_recentProjects.reserve(config.projects.size());
for (auto const &p : config.projects) {
m_recentProjects.emplace_back(p.projectPath);
}
});
return m_projectExplorer.refreshProjectTreeModel();
}
@@ -558,7 +705,10 @@ ox::Error StudioUI::openFile(ox::StringViewCR path) noexcept {
ox::Error StudioUI::openFileActiveTab(ox::StringViewCR path, bool const makeActiveTab) noexcept {
if (!m_project) {
return ox::Error(1, "No project open to open a file from");
return ox::Error(1, "no project open to open a file from");
}
if (!m_project->romFs().exists(path)) {
return ox::Error{1, "file does note exist"};
}
if (m_openFiles.contains(path)) {
for (auto &e : m_editors) {
@@ -572,10 +722,10 @@ ox::Error StudioUI::openFileActiveTab(ox::StringViewCR path, bool const makeActi
}
OX_REQUIRE(ext, fileExt(path));
// create Editor
BaseEditor *editor = nullptr;
ox::UPtr<BaseEditor> editor;
auto const err = m_editorMakers.contains(ext) ?
m_editorMakers[ext](path).moveTo(editor) :
ox::makeCatch<ClawEditor>(m_sctx, path).moveTo(editor);
m_editorMakers[ext](path).to<ox::UPtr<BaseEditor>>().moveTo(editor) :
ox::make_unique_catch<ClawEditor>(m_sctx, path).moveTo(editor);
if (err) {
if constexpr(!ox::defines::Debug) {
oxErrf("Could not open Editor: {}\n", toStr(err));
@@ -585,16 +735,18 @@ ox::Error StudioUI::openFileActiveTab(ox::StringViewCR path, bool const makeActi
return err;
}
editor->closed.connect(this, &StudioUI::closeFile);
m_editors.emplace_back(editor);
auto const &e = m_editors.emplace_back(std::move(editor));
m_openFiles.emplace_back(path);
if (makeActiveTab) {
m_activeEditor = m_editors.back().value->get();
m_activeEditorUpdatePending = editor;
m_activeEditorUpdatePending = e.get();
}
// save to config
studio::editConfig<StudioConfig>(keelCtx(m_tctx), [&path](StudioConfig &config) {
if (!config.openFiles.contains(path)) {
config.openFiles.emplace_back(path);
if (auto const pc = config.project()) {
if (!pc->openFiles.contains(path)) {
pc->openFiles.emplace_back(path);
}
}
});
return {};
@@ -618,6 +770,22 @@ ox::Error StudioUI::handleCloseFileResponse(ig::PopupResponse const response) no
return {};
}
ox::Error StudioUI::handleRemoveRecentProjectResponse(ig::PopupResponse const response) noexcept {
if (response == ig::PopupResponse::OK) {
auto const p = std::move(m_recentProjects[m_removeRecentProject.idx]);
studio::editConfig<StudioConfig>(keelCtx(m_tctx), [&p](StudioConfig &config) {
std::ignore = config.projects.erase(
std::remove_if(
config.projects.begin(), config.projects.end(),
[&p](StudioConfig::ProjectConfig const &pc) {
return pc.projectPath == p;
}));
});
return m_recentProjects.erase(m_removeRecentProject.idx).error;
}
return {};
}
ox::Error StudioUI::closeCurrentFile() noexcept {
for (auto &e : m_editors) {
if (m_activeEditor == e.get()) {
@@ -636,7 +804,9 @@ ox::Error StudioUI::closeFile(ox::StringViewCR path) noexcept {
std::ignore = m_openFiles.erase(std::remove(m_openFiles.begin(), m_openFiles.end(), path));
// save to config
studio::editConfig<StudioConfig>(keelCtx(m_tctx), [&](StudioConfig &config) {
std::ignore = config.openFiles.erase(std::remove(config.openFiles.begin(), config.openFiles.end(), path));
if (auto const pc = config.project()) {
std::ignore = pc->openFiles.erase(std::remove(pc->openFiles.begin(), pc->openFiles.end(), path));
}
});
return {};
}

View File

@@ -14,23 +14,23 @@
#include <studio/project.hpp>
#include <studio/task.hpp>
#include "aboutpopup.hpp"
#include "deleteconfirmation.hpp"
#include "makecopypopup.hpp"
#include "newdir.hpp"
#include "newmenu.hpp"
#include "newproject.hpp"
#include "popups/about.hpp"
#include "popups/deleteconfirmation.hpp"
#include "popups/makecopy.hpp"
#include "popups/newdir.hpp"
#include "popups/newmenu.hpp"
#include "popups/newproject.hpp"
#include "projectexplorer.hpp"
#include "renamefile.hpp"
#include "popups/renamefile.hpp"
namespace studio {
class StudioUI: public ox::SignalHandler {
class StudioUI final: public ox::SignalHandler {
friend class StudioUIDrawer;
private:
Context m_sctx;
turbine::Context &m_tctx;
turbine::Context &m_tctx{m_sctx.tctx};
ox::String m_projectDataDir;
ox::UPtr<Project> m_project;
TaskRunner m_taskRunner;
@@ -44,6 +44,7 @@ class StudioUI: public ox::SignalHandler {
bool m_closeActiveTab{};
ox::Vector<ox::Pair<ox::String>> m_queuedMoves;
ox::Vector<ox::Pair<ox::String>> m_queuedDirMoves;
ox::Vector<ox::String> m_recentProjects;
NewMenu m_newMenu{keelCtx(m_tctx)};
AboutPopup m_aboutPopup{m_tctx};
DeleteConfirmation m_deleteConfirmation;
@@ -57,7 +58,11 @@ class StudioUI: public ox::SignalHandler {
MakeCopyPopup m_copyFilePopup;
RenameFile m_renameFile;
NewProject m_newProject{m_projectDataDir};
ox::Array<Widget*, 10> const m_widgets {
struct {
ig::QuestionPopup dlg{"Remove From Recents?", "Unable to load project. Remove from recent projects?"};
size_t idx{};
} m_removeRecentProject;
ox::Array<Widget*, 11> const m_widgets {
&m_closeFileConfirm,
&m_closeAppConfirm,
&m_copyFilePopup,
@@ -68,6 +73,7 @@ class StudioUI: public ox::SignalHandler {
&m_newDirDialog,
&m_renameFile,
&m_messagePopup,
&m_removeRecentProject.dlg,
};
bool m_showProjectExplorer = true;
struct NavAction {
@@ -77,11 +83,13 @@ class StudioUI: public ox::SignalHandler {
ox::Optional<NavAction> m_navAction;
public:
explicit StudioUI(turbine::Context &ctx, ox::StringParam projectDataDir) noexcept;
explicit StudioUI(turbine::Context &tctx, ox::StringParam projectDataDir) noexcept;
void handleKeyEvent(turbine::Key, bool down) noexcept;
void navigateTo(ox::StringParam path, ox::StringParam navArgs) noexcept;
void handleMouseButtonEvent(int btn, bool down) noexcept;
void handleNavigationChange(ox::StringParam path, ox::StringParam navArgs) noexcept;
[[nodiscard]]
constexpr Project *project() noexcept {
@@ -100,9 +108,9 @@ class StudioUI: public ox::SignalHandler {
void drawTabs() noexcept;
void loadEditorMaker(EditorMaker const&editorMaker) noexcept;
void loadEditorMaker(EditorMaker const &editorMaker) noexcept;
void loadModule(Module const&mod) noexcept;
void loadModule(Module const &mod) noexcept;
void loadModules() noexcept;
@@ -124,7 +132,7 @@ class StudioUI: public ox::SignalHandler {
ox::Error renameFile(ox::StringViewCR path) noexcept;
ox::Error handleMoveFile(ox::StringViewCR oldPath, ox::StringViewCR newPath, ox::UUID const&id) noexcept;
ox::Error handleMoveFile(ox::StringViewCR oldPath, ox::StringViewCR newPath, ox::UUID const &id) noexcept;
ox::Error handleDeleteDir(ox::StringViewCR path) noexcept;
@@ -144,6 +152,8 @@ class StudioUI: public ox::SignalHandler {
ox::Error handleCloseFileResponse(ig::PopupResponse response) noexcept;
ox::Error handleRemoveRecentProjectResponse(ig::PopupResponse response) noexcept;
ox::Error closeCurrentFile() noexcept;
ox::Error closeFile(ox::StringViewCR path) noexcept;

View File

@@ -4,21 +4,21 @@
#pragma once
#include <ox/claw/claw.hpp>
#include <ox/fs/fs.hpp>
#include <ox/model/typenamecatcher.hpp>
#include <ox/oc/oc.hpp>
#include <ox/std/buffer.hpp>
#include <ox/std/defines.hpp>
#include <ox/std/fmt.hpp>
#include <ox/std/trace.hpp>
#include <ox/std/string.hpp>
#include <ox/std/trace.hpp>
#include <keel/context.hpp>
namespace studio {
namespace detail {
inline ox::String slashesToPct(ox::StringView str) noexcept {
inline ox::String slashesToPct(ox::StringViewCR str) noexcept {
auto out = ox::String{str};
for (auto&c: out) {
if (c == '/' || c == '\\') {
@@ -30,78 +30,102 @@ inline ox::String slashesToPct(ox::StringView str) noexcept {
}
[[nodiscard]]
ox::String configPath(keel::Context const&ctx) noexcept;
ox::String configPath(keel::Context const &kctx) noexcept;
template<typename T>
ox::Result<T> readConfig(keel::Context &ctx, ox::StringViewCR name) noexcept {
ox::Result<T> readConfig(keel::Context &kctx, ox::StringViewCR name) noexcept {
oxAssert(name != "", "Config type has no TypeName");
auto const path = ox::sfmt("/{}.json", detail::slashesToPct(name));
ox::PassThroughFS fs(configPath(ctx));
ox::PassThroughFS fs(configPath(kctx));
auto const [buff, err] = fs.read(path);
if (err) {
//oxErrf("Could not read config file: {} - {}\n", path, toStr(err));
return err;
}
return ox::readOC<T>(buff);
OX_REQUIRE(id, ox::readClawTypeId(buff));
ox::Result<T> out;
if (id != ox::ModelTypeId_v<T>) {
out = keel::convert<T>(kctx, buff);
} else {
out = ox::readClaw<T>(buff);
}
OX_RETURN_ERROR(out);
OX_RETURN_ERROR(keel::ensureValid(out.value));
return out;
}
template<typename T>
ox::Result<T> readConfig(keel::Context &ctx) noexcept {
ox::Result<T> readConfig(keel::Context &kctx) noexcept {
constexpr auto TypeName = ox::requireModelTypeName<T>();
return readConfig<T>(ctx, TypeName);
return readConfig<T>(kctx, TypeName);
}
template<typename T>
ox::Error writeConfig(keel::Context &ctx, ox::StringViewCR name, T const&data) noexcept {
ox::Error writeConfig(keel::Context &kctx, ox::StringViewCR name, T const &data) noexcept {
oxAssert(name != "", "Config type has no TypeName");
auto const path = ox::sfmt("/{}.json", detail::slashesToPct(name));
ox::PassThroughFS fs(configPath(ctx));
ox::PassThroughFS fs(configPath(kctx));
if (auto const err = fs.mkdir("/", true)) {
//oxErrf("Could not create config directory: {} - {}\n", path, toStr(err));
return err;
}
OX_REQUIRE_M(buff, ox::writeOC(data));
OX_REQUIRE_M(buff, ox::writeClaw(data, ox::ClawFormat::Organic));
*buff.back().value = '\n';
if (auto const err = fs.write(path, buff.data(), buff.size())) {
//oxErrf("Could not read config file: {} - {}\n", path, toStr(err));
return ox::Error(2, "Could not read config file");
return ox::Error{2, "Could not read config file"};
}
return {};
}
template<typename T>
ox::Error writeConfig(keel::Context &ctx, T const&data) noexcept {
ox::Error writeConfig(keel::Context &kctx, T const &data) noexcept {
constexpr auto TypeName = ox::requireModelTypeName<T>();
return writeConfig(ctx, TypeName, data);
return writeConfig(kctx, TypeName, data);
}
template<typename T, typename Func>
void openConfig(keel::Context &ctx, ox::StringViewCR name, Func f) noexcept {
void openConfig(keel::Context &kctx, ox::StringViewCR name, Func f) noexcept {
oxAssert(name != "", "Config type has no TypeName");
auto const [c, err] = readConfig<T>(ctx, name);
auto const [c, err] = readConfig<T>(kctx, name);
oxLogError(err);
f(c);
}
template<typename T, typename Func>
void openConfig(keel::Context &ctx, Func f) noexcept {
void openConfig(keel::Context &kctx, Func f) noexcept {
constexpr auto TypeName = ox::requireModelTypeName<T>();
openConfig<T>(ctx, TypeName, f);
openConfig<T>(kctx, TypeName, f);
}
template<typename T, typename Func>
void editConfig(keel::Context &ctx, ox::StringViewCR name, Func f) noexcept {
void editConfig(keel::Context &kctx, ox::StringViewCR name, Func f) noexcept {
oxAssert(name != "", "Config type has no TypeName");
auto [c, err] = readConfig<T>(ctx, name);
auto [c, err] = readConfig<T>(kctx, name);
oxLogError(err);
f(c);
oxLogError(writeConfig(ctx, name, c));
oxLogError(writeConfig(kctx, name, c));
}
template<typename T, typename Func>
void editConfig(keel::Context &ctx, Func f) noexcept {
void editConfig(keel::Context &kctx, Func f) noexcept {
constexpr auto TypeName = ox::requireModelTypeName<T>();
editConfig<T>(ctx, TypeName, f);
editConfig<T>(kctx, TypeName, f);
}
/**
* Older config files didn't use ClawHeaders, so they can't
* use the normal conversion system.
* Functions like this shouldn't be necessary moving forward.
*/
template<typename T>
ox::Error headerizeConfigFile(keel::Context &kctx, ox::StringViewCR name = ox::ModelTypeName_v<T>) noexcept {
auto const path = ox::sfmt("/{}.json", detail::slashesToPct(name));
ox::PassThroughFS fs(configPath(kctx));
OX_REQUIRE_M(buff, fs.read(path));
OX_REQUIRE_M(cv1, ox::readOC<T>(buff));
OX_RETURN_ERROR(ox::writeClaw(cv1, ox::ClawFormat::Organic).moveTo(buff));
return fs.write(path, buff);
}
}

View File

@@ -15,11 +15,24 @@ namespace studio {
class StudioUI;
struct Context {
StudioUI &ui;
Project *project = nullptr;
turbine::Context &tctx;
Context(StudioUI &pUi, turbine::Context &pTctx) noexcept:
ui(pUi), tctx(pTctx) {}
public:
friend void navigateTo(Context &ctx, ox::StringParam filePath, ox::StringParam navArgs) noexcept;
friend void navigateBack(Context &ctx) noexcept;
friend void navigateForward(Context &ctx) noexcept;
friend StudioUI;
StudioUI &ui;
Project *project = nullptr;
turbine::Context &tctx;
protected:
struct NavPath {
ox::String filePath;
ox::String navArgs;
};
size_t navIdx{};
ox::Vector<NavPath> navStack;
std::function<void(ox::StringParam filePath, ox::StringParam navArgs)> navCallback;
Context(StudioUI &pUi, turbine::Context &pTctx) noexcept:
ui{pUi}, tctx{pTctx} {}
};
[[nodiscard]]
@@ -27,6 +40,15 @@ inline keel::Context &keelCtx(Context &ctx) noexcept {
return keelCtx(ctx.tctx);
}
[[nodiscard]]
inline keel::Context const &keelCtx(Context const &ctx) noexcept {
return keelCtx(ctx.tctx);
}
void navigateTo(Context &ctx, ox::StringParam filePath, ox::StringParam navArgs = "") noexcept;
void navigateBack(Context &ctx) noexcept;
void navigateForward(Context &ctx) noexcept;
}

View File

@@ -13,7 +13,11 @@ class FilePickerPopup {
private:
ox::String m_name;
FileExplorer m_explorer;
struct Explorer: public FileExplorer {
mutable bool opened{};
explicit Explorer(keel::Context &kctx);
void fileOpened(ox::StringViewCR path) const noexcept override;
} m_explorer;
ox::Vector<ox::String> const m_fileExts;
bool m_open{};
@@ -33,6 +37,10 @@ class FilePickerPopup {
ox::Optional<ox::String> draw(Context &ctx) noexcept;
private:
[[nodiscard]]
ox::Optional<ox::String> handlePick() noexcept;
};
}

View File

@@ -46,14 +46,14 @@ ox::Result<T> getDragDropPayload() noexcept {
static_cast<size_t>(payload->DataSize)});
}
ox::Error setDragDropPayload(ox::CStringViewCR name, auto const&obj) noexcept {
ox::Error setDragDropPayload(ox::CStringViewCR name, auto const &obj) noexcept {
OX_REQUIRE(buff, ox::writeClaw(obj, ox::ClawFormat::Metal));
ImGui::SetDragDropPayload(name.c_str(), buff.data(), buff.size());
return {};
}
template<typename T>
ox::Error setDragDropPayload(T const&obj) noexcept {
ox::Error setDragDropPayload(T const &obj) noexcept {
OX_REQUIRE(buff, ox::writeClaw(obj, ox::ClawFormat::Metal));
ImGui::SetDragDropPayload(ox::ModelTypeName_v<T>, buff.data(), buff.size());
return {};
@@ -77,7 +77,7 @@ class DragDropSource {
}
};
auto dragDropSource(auto const&cb, ImGuiDragDropFlags const flags = 0) noexcept {
auto dragDropSource(auto const &cb, ImGuiDragDropFlags const flags = 0) noexcept {
if constexpr(ox::is_same_v<decltype(cb()), ox::Error>) {
if (ig::DragDropSource const tgt{flags}; tgt) [[unlikely]] {
return cb();
@@ -108,7 +108,7 @@ class DragDropTarget {
}
};
auto dragDropTarget(auto const&cb) noexcept {
auto dragDropTarget(auto const &cb) noexcept {
if constexpr(ox::is_same_v<decltype(cb()), ox::Error>) {
if (ig::DragDropTarget const tgt; tgt) [[unlikely]] {
return cb();
@@ -124,7 +124,7 @@ auto dragDropTarget(auto const&cb) noexcept {
class ChildStackItem {
public:
explicit ChildStackItem(ox::CStringViewCR id, ImVec2 const&sz = {}) noexcept;
explicit ChildStackItem(ox::CStringViewCR id, ImVec2 const &sz = {}) noexcept;
~ChildStackItem() noexcept;
};
@@ -146,7 +146,7 @@ class IndentStackItem {
void centerNextWindow(turbine::Context &ctx) noexcept;
bool PushButton(ox::CStringViewCR lbl, ImVec2 const&btnSz = BtnSz) noexcept;
bool PushButton(ox::CStringViewCR lbl, ImVec2 const &btnSz = BtnSz) noexcept;
template<typename Str>
struct TextInput {
@@ -234,7 +234,7 @@ PopupResponse PopupControlsOk(
ox::CStringViewCR ok);
[[nodiscard]]
bool BeginPopup(turbine::Context &ctx, ox::CStringViewCR popupName, bool &show, ImVec2 const&sz = {285, 0});
bool BeginPopup(turbine::Context &ctx, ox::CStringViewCR popupName, bool &show, ImVec2 const &sz = {285, 0});
/**
*
@@ -266,7 +266,7 @@ bool ComboBox(ox::CStringView lbl, ox::Span<const ox::String> list, size_t &sele
*/
bool ComboBox(
ox::CStringViewCR lbl,
std::function<ox::CStringView(size_t)> const&f,
std::function<ox::CStringView(size_t)> const &f,
size_t strCnt,
size_t &selectedIdx) noexcept;
@@ -278,10 +278,10 @@ bool FileComboBox(
bool ListBox(
ox::CStringViewCR name,
std::function<ox::CStringView(size_t)> const&f,
std::function<ox::CStringView(size_t)> const &f,
size_t strCnt,
size_t &selIdx,
ImVec2 const&sz = {0, 0}) noexcept;
ImVec2 const &sz = {0, 0}) noexcept;
/**
*
@@ -290,7 +290,7 @@ bool ListBox(
* @param selIdx
* @return true if new value selected, false otherwise
*/
bool ListBox(ox::CStringViewCR name, ox::SpanView<ox::String> const&list, size_t &selIdx) noexcept;
bool ListBox(ox::CStringViewCR name, ox::SpanView<ox::String> const &list, size_t &selIdx) noexcept;
class FilePicker {
private:
@@ -306,7 +306,7 @@ class FilePicker {
studio::Context &sctx,
ox::StringParam title,
ox::StringParam fileExt,
ImVec2 const&size = {}) noexcept;
ImVec2 const &size = {}) noexcept;
void draw() noexcept;
@@ -348,6 +348,8 @@ class QuestionPopup: public Popup {
QuestionPopup(ox::StringParam title, ox::StringParam question) noexcept;
void setQuestion(ox::StringParam msg) noexcept;
void draw(Context &ctx) noexcept override;
};

View File

@@ -109,7 +109,7 @@ class ItemMaker {
return m_typeDisplayName;
}
bool installTemplate(ox::UPtr<ItemTemplate> &tmpl) {
bool installTemplate(ox::UPtr<ItemTemplate> &&tmpl) {
if (typeName() == tmpl->typeName() &&
typeVersion() <= tmpl->typeVersion()) {
m_templates.emplace_back(std::move(tmpl));
@@ -120,10 +120,6 @@ class ItemMaker {
return false;
}
bool installTemplate(ox::UPtr<ItemTemplate> &&tmpl) {
return installTemplate(tmpl);
}
constexpr ox::Vector<ox::UPtr<ItemTemplate>> const&itemTemplates() const noexcept {
return m_templates;
}

View File

@@ -1,6 +1,7 @@
add_library(
Studio
configio.cpp
context.cpp
editor.cpp
filepickerpopup.cpp
filetreemodel.cpp

View File

@@ -23,7 +23,7 @@ constexpr auto ConfigDir = [] {
}
}();
ox::String configPath(keel::Context const&ctx) noexcept {
ox::String configPath(keel::Context const &ctx) noexcept {
auto const homeDir = std::getenv(ox::defines::OS == ox::OS::Windows ? "USERPROFILE" : "HOME");
return ox::sfmt(ConfigDir, homeDir, ctx.appName);
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <studio/context.hpp>
namespace studio {
void navigateTo(Context &ctx, ox::StringParam filePath, ox::StringParam navArgs) noexcept {
ox::String path = std::move(filePath);
if (beginsWith(path, "uuid://")) {
auto const [p, err] = keel::uuidUrlToPath(keelCtx(ctx), path);
if (err) {
return;
}
path = p;
}
ctx.navStack.resize(ctx.navIdx + 1);
ctx.navStack.emplace_back(ox::String{path}, ox::String{navArgs.view()});
try {
ctx.navCallback(std::move(path), std::move(navArgs));
} catch (std::exception const &e) {
oxErrf("Nav error: {}", e.what());
oxAssert(false, "Nav error");
}
}
void navigateBack(Context &ctx) noexcept {
if (!ctx.navIdx) {
return;
}
--ctx.navIdx;
while (ctx.navIdx < ctx.navStack.size() && ctx.navIdx) {
auto const i = ctx.navIdx - 1;
auto const &n = ctx.navStack[i];
if (!ctx.project->exists(n.filePath)) {
std::ignore = ctx.navStack.erase(i);
--ctx.navIdx;
continue;
}
try {
ctx.navCallback(n.filePath, n.navArgs);
} catch (std::exception const &e) {
oxAssert(ctx.navCallback != nullptr, "navCallback is null");
oxErrf("navigateForward failed: {}", e.what());
}
break;
}
}
void navigateForward(Context &ctx) noexcept {
while (ctx.navIdx < ctx.navStack.size()) {
auto const &n = ctx.navStack[ctx.navIdx];
try {
ctx.navCallback(n.filePath, n.navArgs);
} catch (std::exception const &e) {
oxAssert(ctx.navCallback != nullptr, "navCallback is null");
oxErrf("navigateForward failed: {}", e.what());
}
if (!ctx.project->exists(n.filePath)) {
std::ignore = ctx.navStack.erase(ctx.navIdx);
continue;
}
++ctx.navIdx;
break;
}
}
}

View File

@@ -72,8 +72,10 @@ bool BaseEditor::exportable() const noexcept {
}
void BaseEditor::setCutEnabled(bool v) {
m_cutEnabled = v;
cutEnabledChanged.emit(v);
if (m_cutEnabled != v) {
m_cutEnabled = v;
cutEnabledChanged.emit(v);
}
}
bool BaseEditor::cutEnabled() const noexcept {
@@ -81,8 +83,10 @@ bool BaseEditor::cutEnabled() const noexcept {
}
void BaseEditor::setCopyEnabled(bool v) {
m_copyEnabled = v;
copyEnabledChanged.emit(v);
if (m_copyEnabled != v) {
m_copyEnabled = v;
copyEnabledChanged.emit(v);
}
}
bool BaseEditor::copyEnabled() const noexcept {
@@ -90,8 +94,10 @@ bool BaseEditor::copyEnabled() const noexcept {
}
void BaseEditor::setPasteEnabled(bool v) {
m_pasteEnabled = v;
pasteEnabledChanged.emit(v);
if (m_pasteEnabled != v) {
m_pasteEnabled = v;
pasteEnabledChanged.emit(v);
}
}
bool BaseEditor::pasteEnabled() const noexcept {

View File

@@ -8,6 +8,15 @@
namespace studio {
FilePickerPopup::Explorer::Explorer(keel::Context &kctx):
FileExplorer{kctx} {
}
void FilePickerPopup::Explorer::fileOpened(ox::StringViewCR) const noexcept {
opened = true;
}
FilePickerPopup::FilePickerPopup(
ox::StringParam name,
keel::Context &kctx,
@@ -41,6 +50,7 @@ void FilePickerPopup::refresh() noexcept {
void FilePickerPopup::open() noexcept {
refresh();
m_open = true;
m_explorer.opened = false;
}
void FilePickerPopup::close() noexcept {
@@ -60,16 +70,22 @@ ox::Optional<ox::String> FilePickerPopup::draw(Context &ctx) noexcept {
if (ig::BeginPopup(ctx.tctx, m_name, m_open, {380, 340})) {
auto const vp = ImGui::GetContentRegionAvail();
m_explorer.draw(ctx, {vp.x, vp.y - 30});
if (ig::PopupControlsOkCancel(m_open) == ig::PopupResponse::OK) {
auto p = m_explorer.selectedPath();
if (p) {
out.emplace(*p);
}
close();
if (ig::PopupControlsOkCancel(m_open) == ig::PopupResponse::OK || m_explorer.opened) {
out = handlePick();
}
ImGui::EndPopup();
}
return out;
}
ox::Optional<ox::String> FilePickerPopup::handlePick() noexcept {
ox::Optional<ox::String> out;
auto p = m_explorer.selectedPath();
if (p) {
out.emplace(*p);
}
close();
return out;
}
}

View File

@@ -10,7 +10,7 @@
namespace studio::ig {
ChildStackItem::ChildStackItem(ox::CStringViewCR id, ImVec2 const&sz) noexcept {
ChildStackItem::ChildStackItem(ox::CStringViewCR id, ImVec2 const &sz) noexcept {
ImGui::BeginChild(id.c_str(), sz);
}
@@ -50,7 +50,7 @@ void centerNextWindow(turbine::Context &ctx) noexcept {
ImGui::SetNextWindowPos(ImVec2(screenW / mod, screenH / mod), ImGuiCond_Always, ImVec2(0.5f, 0.5f));
}
bool PushButton(ox::CStringViewCR lbl, ImVec2 const&btnSz) noexcept {
bool PushButton(ox::CStringViewCR lbl, ImVec2 const &btnSz) noexcept {
return ImGui::Button(lbl.c_str(), btnSz);
}
@@ -101,7 +101,7 @@ PopupResponse PopupControlsOk(
return out;
}
bool BeginPopup(turbine::Context &ctx, ox::CStringViewCR popupName, bool &show, ImVec2 const&sz) {
bool BeginPopup(turbine::Context &ctx, ox::CStringViewCR popupName, bool &show, ImVec2 const &sz) {
constexpr auto modalFlags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize;
centerNextWindow(ctx);
ImGui::OpenPopup(popupName.c_str());
@@ -149,7 +149,7 @@ bool ComboBox(
bool ComboBox(
ox::CStringViewCR lbl,
std::function<ox::CStringView(size_t)> const&f,
std::function<ox::CStringView(size_t)> const &f,
size_t strCnt,
size_t &selectedIdx) noexcept {
bool out{};
@@ -172,16 +172,16 @@ bool FileComboBox(
Context &sctx,
ox::StringViewCR fileExt,
size_t &selectedIdx) noexcept {
auto const&list = sctx.project->fileList(fileExt);
auto const &list = sctx.project->fileList(fileExt);
return ComboBox(lbl, list, selectedIdx);
}
bool ListBox(
ox::CStringViewCR name,
std::function<ox::CStringView(size_t)> const&f,
std::function<ox::CStringView(size_t)> const &f,
size_t const strCnt,
size_t &selIdx,
ImVec2 const&sz) noexcept {
ImVec2 const &sz) noexcept {
auto out = false;
if (ImGui::BeginListBox(name.c_str(), sz)) {
for (size_t i = 0; i < strCnt; ++i) {
@@ -199,13 +199,13 @@ bool ListBox(
return out;
}
bool ListBox(ox::CStringViewCR name, ox::SpanView<ox::String> const&list, size_t &selIdx) noexcept {
bool ListBox(ox::CStringViewCR name, ox::SpanView<ox::String> const &list, size_t &selIdx) noexcept {
return ListBox(name, [list](size_t i) -> ox::CStringView {
return list[i];
}, list.size(), selIdx);
}
bool ListBox(ox::CStringViewCR name, ox::SpanView<ox::CStringView> const&list, size_t &selIdx) noexcept {
bool ListBox(ox::CStringViewCR name, ox::SpanView<ox::CStringView> const &list, size_t &selIdx) noexcept {
return ListBox(name, [list](size_t i) -> ox::CStringView {
return list[i];
}, list.size(), selIdx);
@@ -216,7 +216,7 @@ FilePicker::FilePicker(
Context &sctx,
ox::StringParam title,
ox::StringParam fileExt,
ImVec2 const&size) noexcept:
ImVec2 const &size) noexcept:
m_sctx(sctx),
m_title(std::move(title)),
m_fileExt(std::move(fileExt)),
@@ -230,7 +230,7 @@ void FilePicker::draw() noexcept {
auto constexpr popupSz = ImVec2{450.f, 0};
IDStackItem const idStackItem(m_title);
if (BeginPopup(m_sctx.tctx, m_title, m_show, popupSz)) {
auto const&list = m_sctx.project->fileList(m_fileExt);
auto const &list = m_sctx.project->fileList(m_fileExt);
size_t selIdx{};
ComboBox(m_title, list, selIdx);
if (PopupControlsOkCancel(popupSz.x, m_show) == ig::PopupResponse::OK) {
@@ -267,6 +267,10 @@ QuestionPopup::QuestionPopup(ox::StringParam title, ox::StringParam question) no
m_question{std::move(question)} {
}
void QuestionPopup::setQuestion(ox::StringParam msg) noexcept {
m_question = std::move(msg);
}
void QuestionPopup::draw(Context &ctx) noexcept {
switch (m_stage) {
case Stage::Closed:
@@ -275,6 +279,9 @@ void QuestionPopup::draw(Context &ctx) noexcept {
ImGui::OpenPopup(m_title.c_str());
m_stage = Stage::Open;
m_open = true;
// require extended refresh in case new contents require resize
// this can take around a second
turbine::requireRefreshFor(ctx.tctx, 1000);
[[fallthrough]];
case Stage::Open:
centerNextWindow(ctx.tctx);

View File

@@ -101,8 +101,8 @@ ox::Result<ox::FileStat> Project::stat(ox::StringViewCR path) const noexcept {
ox::Error Project::copyItem(ox::StringViewCR src, ox::StringViewCR dest) noexcept {
OX_REQUIRE_M(buff, loadBuff(src));
OX_REQUIRE(id, keel::regenerateUuidHeader(buff));
OX_RETURN_ERROR(writeBuff(dest, buff));
createUuidMapping(m_kctx, dest, id);
OX_RETURN_ERROR(writeBuff(dest, ox::BufferView{buff} + keel::K1HdrSz));
return {};
}

View File

@@ -49,6 +49,19 @@ ox::Error setWindowBounds(Context &ctx, ox::Bounds const&bnds) noexcept;
* @param ctx - Context
* @param ms - milliseconds
*/
void setRefreshWithin(Context &ctx, int ms) noexcept;
void requireRefreshWithin(Context &ctx, int ms) noexcept;
/**
* Stimulates screen to draw for a period of a short period of
* time (168 ms on GLFW implementation).
* @param ctx - Context
*/
void requireRefresh(Context &ctx) noexcept;
/**
* Stimulates screen to draw for a specified period of time.
* @param ctx - Context
*/
void requireRefreshFor(Context &ctx, int ms) noexcept;
}

View File

@@ -79,11 +79,14 @@ enum Key {
};
using KeyEventHandler = void(*)(Context&, Key, bool);
using MouseButtonEventHandler = void(*)(Context&, int btn, bool);
void setKeyEventHandler(Context &ctx, KeyEventHandler h) noexcept;
void setMouseButtonEventHandler(Context &ctx, MouseButtonEventHandler h) noexcept;
[[nodiscard]]
KeyEventHandler keyEventHandler(Context &ctx) noexcept;
KeyEventHandler keyEventHandler(Context const &ctx) noexcept;
[[nodiscard]]
bool buttonDown(Context const&ctx, Key) noexcept;

View File

@@ -133,7 +133,9 @@ void setKeyEventHandler(Context &ctx, KeyEventHandler h) noexcept {
ctx.keyEventHandler = h;
}
KeyEventHandler keyEventHandler(Context &ctx) noexcept {
void setMouseButtonEventHandler(Context&, MouseButtonEventHandler) noexcept {}
KeyEventHandler keyEventHandler(Context const &ctx) noexcept {
return ctx.keyEventHandler;
}

View File

@@ -16,6 +16,7 @@ class Context {
UpdateHandler updateHandler = [](Context&) -> int {return -1;};
keel::Context keelCtx;
KeyEventHandler keyEventHandler = nullptr;
MouseButtonEventHandler mouseButtonEventHandler = nullptr;
ox::AnyPtr applicationData;
// GLFW impl data ////////////////////////////////////////////////////////

View File

@@ -155,6 +155,10 @@ static ox::Result<IconData> toGlfwImgPixels(ox::SpanView<uint8_t> const &iconPng
return out;
}
static void setMandatoryRefreshPeriod(Context &ctx, TimeMs const newVal) {
ctx.mandatoryRefreshPeriodEnd = ox::max(ctx.mandatoryRefreshPeriodEnd, newVal);
}
ox::Error setWindowIcon(Context &ctx, ox::SpanView<ox::SpanView<uint8_t>> const &iconPngs) noexcept {
if constexpr(ox::defines::OS != ox::OS::Darwin) {
ox::Vector<IconData, 8> src;
@@ -216,10 +220,19 @@ ox::Error setWindowBounds(Context &ctx, ox::Bounds const&bnds) noexcept {
return {};
}
void setRefreshWithin(Context &ctx, int ms) noexcept {
void requireRefreshWithin(Context &ctx, int const ms) noexcept {
ctx.refreshWithinMs = ox::min(ms, ctx.refreshWithinMs);
}
void requireRefresh(Context &ctx) noexcept {
setMandatoryRefreshPeriod(ctx, ticksMs(ctx) + config::MandatoryRefreshPeriod);
}
void requireRefreshFor(Context &ctx, int ms) noexcept {
ms = ox::max(0, ms);
setMandatoryRefreshPeriod(ctx, ticksMs(ctx) + static_cast<TimeMs>(ms));
}
static void draw(Context &ctx) noexcept {
// draw start
#if TURBINE_USE_IMGUI
@@ -286,14 +299,19 @@ static void handleKeyPress(Context &ctx, int const key, bool const down) noexcep
static void handleGlfwCursorPosEvent(GLFWwindow*, double, double) noexcept {
}
static void handleGlfwMouseButtonEvent(GLFWwindow *window, int, int, int) noexcept {
static void handleGlfwMouseButtonEvent(
GLFWwindow *window,
int const btn,
int const action,
int) noexcept {
auto &ctx = *static_cast<Context*>(glfwGetWindowUserPointer(window));
ctx.mandatoryRefreshPeriodEnd = ticksMs(ctx) + config::MandatoryRefreshPeriod;
setMandatoryRefreshPeriod(ctx, ticksMs(ctx) + config::MandatoryRefreshPeriod);
ctx.mouseButtonEventHandler(ctx, btn, action == 1);
}
static void handleGlfwKeyEvent(GLFWwindow *window, int const key, int, int const action, int) noexcept {
auto &ctx = *static_cast<Context*>(glfwGetWindowUserPointer(window));
ctx.mandatoryRefreshPeriodEnd = ticksMs(ctx) + config::MandatoryRefreshPeriod;
setMandatoryRefreshPeriod(ctx, ticksMs(ctx) + config::MandatoryRefreshPeriod);
if (action == GLFW_PRESS) {
handleKeyPress(ctx, key, true);
} else if (action == GLFW_RELEASE) {
@@ -303,7 +321,7 @@ static void handleGlfwKeyEvent(GLFWwindow *window, int const key, int, int const
static void handleGlfwWindowCloseEvent(GLFWwindow *window) noexcept {
auto &ctx = *static_cast<Context*>(glfwGetWindowUserPointer(window));
ctx.mandatoryRefreshPeriodEnd = ticksMs(ctx) + config::MandatoryRefreshPeriod;
setMandatoryRefreshPeriod(ctx, ticksMs(ctx) + config::MandatoryRefreshPeriod);
ctx.running = ctx.shutdownHandler ? !ctx.shutdownHandler(ctx) : false;
glfwSetWindowShouldClose(window, !ctx.running);
glfwPostEmptyEvent();
@@ -316,7 +334,7 @@ ox::Result<ox::UPtr<Context>> init(
using namespace std::chrono;
ctx->startTime = static_cast<TimeMs>(
duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count());
ctx->mandatoryRefreshPeriodEnd = ticksMs(*ctx) + config::MandatoryRefreshPeriod;
setMandatoryRefreshPeriod(*ctx, ticksMs(*ctx) + config::MandatoryRefreshPeriod);
// init GLFW context
glfwInit();
glfwSetErrorCallback(handleGlfwError);
@@ -326,7 +344,8 @@ ox::Result<ox::UPtr<Context>> init(
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);
constexpr auto Scale = 5;
ctx->window = glfwCreateWindow(240 * Scale, 160 * Scale, ctx->keelCtx.appName.c_str(), nullptr, nullptr);
ctx->window = glfwCreateWindow(
240 * Scale, 160 * Scale, ctx->keelCtx.appName.c_str(), nullptr, nullptr);
//ctx.window = glfwCreateWindow(876, 743, ctx.keelCtx.appName.c_str(), nullptr, nullptr);
if (ctx->window == nullptr) {
return ox::Error(1, "Could not open GLFW window");
@@ -389,14 +408,14 @@ ox::Error run(Context &ctx) noexcept {
glfwPollEvents();
auto const ticks = ticksMs(ctx);
if (ctx.wakeupTime <= ticks) {
auto const st = ctx.updateHandler(ctx);
if (st >= 0) {
ctx.wakeupTime = ticks + static_cast<unsigned>(st);
sleepTime = static_cast<uint64_t>(st);
} else {
ctx.wakeupTime = ~uint64_t{0};
sleepTime = ctx.wakeupTime - ticks;
}
auto const st = ctx.updateHandler(ctx);
if (st >= 0) {
ctx.wakeupTime = ticks + static_cast<unsigned>(st);
sleepTime = static_cast<uint64_t>(st);
} else {
ctx.wakeupTime = ~uint64_t{0};
sleepTime = ctx.wakeupTime - ticks;
}
} else {
sleepTime = ctx.wakeupTime - ticks;
}
@@ -434,15 +453,19 @@ void setShutdownHandler(Context &ctx, ShutdownHandler const handler) noexcept {
ctx.shutdownHandler = handler;
}
void setUpdateHandler(Context &ctx, UpdateHandler h) noexcept {
void setUpdateHandler(Context &ctx, UpdateHandler const h) noexcept {
ctx.updateHandler = h;
}
void setKeyEventHandler(Context &ctx, KeyEventHandler h) noexcept {
void setKeyEventHandler(Context &ctx, KeyEventHandler const h) noexcept {
ctx.keyEventHandler = h;
}
KeyEventHandler keyEventHandler(Context &ctx) noexcept {
void setMouseButtonEventHandler(Context &ctx, MouseButtonEventHandler const h) noexcept {
ctx.mouseButtonEventHandler = h;
}
KeyEventHandler keyEventHandler(Context const &ctx) noexcept {
return ctx.keyEventHandler;
}

59
util/scripts/loc.py Executable file
View File

@@ -0,0 +1,59 @@
#! /usr/bin/env python3
from pathlib import Path
import argparse
def parse_args():
parser = argparse.ArgumentParser(description="Count and sort lines of code in selected files.")
parser.add_argument(
"--search-dirs", nargs="+", required=True,
help="List of directories to search (recursively)."
)
parser.add_argument(
"--include-exts", nargs="+", required=True,
help="List of file extensions to include (e.g. .cpp .hpp .py)"
)
parser.add_argument(
"--exclude-paths", nargs="*", default=[],
help="Full file paths to exclude from counting."
)
return parser.parse_args()
def main():
args = parse_args()
include_exts = tuple(args.include_exts)
exclude_paths = {Path(p).resolve() for p in args.exclude_paths}
files = []
for base in args.search_dirs:
path = Path(base)
if path.is_dir():
for file in path.rglob("*"):
try:
resolved_file = file.resolve()
if (
file.is_file()
and file.name.endswith(include_exts)
and resolved_file not in exclude_paths
):
files.append(str(resolved_file))
except FileNotFoundError:
continue
line_counts = []
total_lines = 0
for f in files:
try:
with open(f, "r", encoding="utf-8", errors="ignore") as file:
lines = sum(1 for _ in file)
line_counts.append((lines, f))
total_lines += lines
except Exception as e:
print(f"Failed to read {f}: {e}")
line_counts.sort()
for count, filename in line_counts:
print(f"{count:>7} {filename}")
print(f"{total_lines:>7} total")
if __name__ == "__main__":
main()