Compare commits

...

5 Commits

Author SHA1 Message Date
f840240aac [nostalgia/gfx/studio/tilesheeteditor] Rework system for tracking current palette path
All checks were successful
Build / build (push) Successful in 3m29s
2025-01-25 22:59:51 -06:00
cfa91d3d39 [keel,studio] Add ability to rename files 2025-01-25 22:59:01 -06:00
f7a7a66a6a [ox/event] Add Signal::connectionCnt 2025-01-25 22:58:18 -06:00
5145595d57 [ox/std] Fix HashMap collision handling 2025-01-25 22:16:42 -06:00
f01d303381 [ox/std] Fix UPtr compare with nullptr 2025-01-25 20:13:47 -06:00
22 changed files with 366 additions and 128 deletions

View File

@@ -143,6 +143,11 @@ class Signal {
Error disconnectObject(const void *receiver) const noexcept;
[[nodiscard]]
size_t connectionCnt() const noexcept {
return m_slots.size();
}
void emit(Args... args) const;
Error emitCheckError(Args... args) const noexcept;
@@ -319,6 +324,11 @@ class Signal<Error(Args...)> {
Error disconnectObject(const void *receiver) const noexcept;
[[nodiscard]]
size_t connectionCnt() const noexcept {
return m_slots.size();
}
void emit(Args... args) const noexcept;
Error emitCheckError(Args... args) const noexcept;

View File

@@ -11,6 +11,7 @@
#include "algorithm.hpp"
#include "hash.hpp"
#include "ignore.hpp"
#include "optional.hpp"
#include "stringview.hpp"
#include "strops.hpp"
#include "vector.hpp"
@@ -26,11 +27,12 @@ class HashMap {
private:
struct Pair {
UPtr<Pair> next;
K key = {};
T value{};
};
Vector<K> m_keys;
Vector<Pair*> m_pairs;
Vector<UPtr<Pair>> m_pairs;
public:
explicit constexpr HashMap(std::size_t size = 127);
@@ -73,10 +75,10 @@ class HashMap {
constexpr void expand();
template<typename KK>
constexpr Pair *const&access(Vector<Pair*> const&pairs, KK const&key) const;
constexpr UPtr<Pair> const &access(Vector<UPtr<Pair>> const &pairs, KK const &key) const;
template<typename KK>
constexpr Pair *&access(Vector<Pair*> &pairs, KK const&key);
constexpr UPtr<Pair> &access(Vector<UPtr<Pair>> &pairs, KK const &key);
};
@@ -85,14 +87,13 @@ constexpr HashMap<K, T>::HashMap(std::size_t size): m_pairs(size) {
}
template<typename K, typename T>
constexpr HashMap<K, T>::HashMap(HashMap<K, T> const&other) {
m_pairs = other.m_pairs;
constexpr HashMap<K, T>::HashMap(HashMap const &other) {
operator=(other);
}
template<typename K, typename T>
constexpr HashMap<K, T>::HashMap(HashMap<K, T> &&other) noexcept {
m_keys = std::move(other.m_keys);
m_pairs = std::move(other.m_pairs);
constexpr HashMap<K, T>::HashMap(HashMap &&other) noexcept {
operator=(std::move(other));
}
template<typename K, typename T>
@@ -115,19 +116,25 @@ constexpr bool HashMap<K, T>::operator==(HashMap const&other) const {
}
template<typename K, typename T>
constexpr HashMap<K, T> &HashMap<K, T>::operator=(HashMap<K, T> const&other) {
constexpr HashMap<K, T> &HashMap<K, T>::operator=(HashMap const &other) {
if (this != &other) {
clear();
m_keys = other.m_keys;
m_pairs = other.m_pairs;
m_pairs.resize(other.m_pairs.size());
for (auto const&k : m_keys) {
auto const &src = access(other.m_pairs, k);
auto &dst = access(m_pairs, k);
dst = ox::make_unique<Pair>();
dst->key = src->key;
dst->value = src->value;
}
}
return *this;
}
template<typename K, typename T>
constexpr HashMap<K, T> &HashMap<K, T>::operator=(HashMap<K, T> &&other) noexcept {
constexpr HashMap<K, T> &HashMap<K, T>::operator=(HashMap &&other) noexcept {
if (this != &other) {
clear();
m_keys = std::move(other.m_keys);
m_pairs = std::move(other.m_pairs);
}
@@ -135,60 +142,52 @@ constexpr HashMap<K, T> &HashMap<K, T>::operator=(HashMap<K, T> &&other) noexcep
}
template<typename K, typename T>
constexpr T &HashMap<K, T>::operator[](MaybeView_t<K> const&k) {
auto p = &access(m_pairs, k);
constexpr T &HashMap<K, T>::operator[](MaybeView_t<K> const &key) {
auto p = &access(m_pairs, key);
if (*p == nullptr) {
if (static_cast<double>(m_pairs.size()) * 0.7 <
static_cast<double>(m_keys.size())) {
expand();
p = &access(m_pairs, k);
p = &access(m_pairs, key);
}
*p = new Pair;
(*p)->key = k;
m_keys.emplace_back(k);
*p = ox::make_unique<Pair>();
(*p)->key = key;
m_keys.emplace_back(key);
}
return (*p)->value;
}
template<typename K, typename T>
constexpr Result<T*> HashMap<K, T>::at(MaybeView_t<K> const&k) noexcept {
auto p = access(m_pairs, k);
constexpr Result<T*> HashMap<K, T>::at(MaybeView_t<K> const &key) noexcept {
auto &p = access(m_pairs, key);
if (!p) {
return {nullptr, ox::Error(1, "value not found for given key")};
return ox::Error{1, "value not found for given key"};
}
return &p->value;
}
template<typename K, typename T>
constexpr Result<const T*> HashMap<K, T>::at(MaybeView_t<K> const&k) const noexcept {
auto p = access(m_pairs, k);
constexpr Result<const T*> HashMap<K, T>::at(MaybeView_t<K> const &key) const noexcept {
auto &p = access(m_pairs, key);
if (!p) {
return {nullptr, ox::Error(1, "value not found for given key")};
return ox::Error{1, "value not found for given key"};
}
return &p->value;
}
template<typename K, typename T>
constexpr void HashMap<K, T>::erase(MaybeView_t<K> const&k) {
if (!contains(k)) {
constexpr void HashMap<K, T>::erase(MaybeView_t<K> const &key) {
if (!contains(key)) {
return;
}
auto h = ox::hash<MaybeView_t<K>>{}(k) % m_pairs.size();
while (true) {
const auto &p = m_pairs[h];
if (p == nullptr || p->key == k) {
std::ignore = m_pairs.erase(h);
break;
} else {
h = ox::hash<MaybeView_t<K>>{}(k) % m_pairs.size();
}
}
std::ignore = m_keys.erase(ox::find(m_keys.begin(), m_keys.end(), k));
auto &c = access(m_pairs, key);
c = std::move(c->next);
std::ignore = m_keys.erase(ox::find(m_keys.begin(), m_keys.end(), key));
}
template<typename K, typename T>
constexpr bool HashMap<K, T>::contains(MaybeView_t<K> const&k) const noexcept {
return access(m_pairs, k) != nullptr;
constexpr bool HashMap<K, T>::contains(MaybeView_t<K> const &key) const noexcept {
return access(m_pairs, key).get() != nullptr;
}
template<typename K, typename T>
@@ -204,25 +203,24 @@ constexpr Vector<K> const&HashMap<K, T>::keys() const noexcept {
template<typename K, typename T>
constexpr Vector<T> HashMap<K, T>::values() const noexcept {
Vector<T> out;
out.reserve(m_pairs.size());
out.reserve(m_keys.size());
for (auto const &p : m_pairs) {
if (out) {
out.emplace_back(p->value);
}
}
return out;
}
template<typename K, typename T>
constexpr void HashMap<K, T>::clear() {
for (std::size_t i = 0; i < m_pairs.size(); i++) {
delete m_pairs[i];
}
m_pairs.clear();
m_pairs.resize(127);
}
template<typename K, typename T>
constexpr void HashMap<K, T>::expand() {
Vector<Pair*> r(m_pairs.size() * 2);
Vector<UPtr<Pair>> r{m_pairs.size() * 2};
for (std::size_t i = 0; i < m_keys.size(); ++i) {
auto const &k = m_keys[i];
access(r, k) = std::move(access(m_pairs, k));
@@ -232,29 +230,39 @@ constexpr void HashMap<K, T>::expand() {
template<typename K, typename T>
template<typename KK>
constexpr typename HashMap<K, T>::Pair *const&HashMap<K, T>::access(Vector<Pair*> const&pairs, KK const&k) const {
auto h = static_cast<std::size_t>(ox::hash<KK>{}(k) % pairs.size());
while (true) {
constexpr UPtr<typename HashMap<K, T>::Pair> const &HashMap<K, T>::access(
Vector<UPtr<Pair>> const& pairs,
KK const &key) const {
auto const h = static_cast<std::size_t>(ox::hash<KK>{}(key) % pairs.size());
auto const &p = *pairs.at(h).unwrap();
if (p == nullptr || p->key == k) {
if (p == nullptr || p->key == key) {
return p;
} else {
h = (h + 1) % pairs.size();
}
auto c = &p->next;
while (true) {
if (*c == nullptr || (*c)->key == key) {
return *c;
}
c = &(*c)->next;
}
}
template<typename K, typename T>
template<typename KK>
constexpr typename HashMap<K, T>::Pair *&HashMap<K, T>::access(Vector<Pair*> &pairs, KK const&k) {
auto h = static_cast<std::size_t>(ox::hash<KK>{}(k) % pairs.size());
while (true) {
constexpr UPtr<typename HashMap<K, T>::Pair> &HashMap<K, T>::access(
Vector<UPtr<Pair>> &pairs,
KK const &key) {
auto const h = static_cast<std::size_t>(ox::hash<KK>{}(key) % pairs.size());
auto &p = *pairs.at(h).unwrap();
if (p == nullptr || p->key == k) {
if (p == nullptr || p->key == key) {
return p;
} else {
h = (h + 1) % pairs.size();
}
auto c = &p->next;
while (true) {
if (*c == nullptr || (*c)->key == key) {
return *c;
}
c = &(*c)->next;
}
}

View File

@@ -260,12 +260,12 @@ constexpr bool operator==(const UniquePtr<T> &p1, const UniquePtr<T> &p2) noexce
template<typename T>
constexpr bool operator==(const UniquePtr<T> &p1, std::nullptr_t) noexcept {
return p1.get();
return p1.get() == nullptr;
}
template<typename T>
constexpr bool operator==(std::nullptr_t, const UniquePtr<T> &p2) noexcept {
return p2.get();
return p2.get() == nullptr;
}

View File

@@ -328,6 +328,16 @@ OX_CLANG_NOWARN_END
si["aoeu"] = 100;
oxAssert(si["asdf"] == 42, "asdf != 42");
oxAssert(si["aoeu"] == 100, "aoeu != 100");
si.erase("asdf");
oxAssert(!si.contains("asdf"), "wrongly contains asdf");
oxAssert(si.contains("aoeu"), "does not contains aoeu");
oxAssert(!si.at("asdf").ok(), "asdf != 0");
oxExpect(si["asdf"], 0);
oxAssert(si["aoeu"] == 100, "aoeu != 100");
auto si2 = si;
oxDebugf("{}", si2["asdf"]);
oxExpect(si2["asdf"], 0);
oxAssert(si2["aoeu"] == 100, "aoeu != 100");
ox::HashMap<int, int> ii;
ii[4] = 42;
ii[5] = 100;

View File

@@ -70,8 +70,8 @@ static ox::Error toPngFile(
ox::Vector<uint32_t> &&pixels,
Palette const&pal,
size_t page,
unsigned width,
unsigned height) noexcept {
unsigned const width,
unsigned const height) noexcept {
for (auto &c : pixels) {
c = color32(color(pal, page, c)) | static_cast<Color32>(0XFF << 24);
}
@@ -96,7 +96,6 @@ TileSheetEditorImGui::TileSheetEditorImGui(studio::StudioContext &sctx, ox::Stri
// connect signal/slots
m_subsheetEditor.inputSubmitted.connect(this, &TileSheetEditorImGui::updateActiveSubsheet);
m_exportMenu.inputSubmitted.connect(this, &TileSheetEditorImGui::exportSubhseetToPng);
m_model.paletteChanged.connect(this, &TileSheetEditorImGui::setPaletteSelection);
// load config
auto const&config = studio::readConfig<TileSheetEditorConfig>(
keelCtx(m_sctx), itemPath());
@@ -125,7 +124,7 @@ bool TileSheetEditorImGui::acceptsClipboardPayload() const noexcept {
return m_model.acceptsClipboardPayload();
}
void TileSheetEditorImGui::keyStateChanged(turbine::Key key, bool down) {
void TileSheetEditorImGui::keyStateChanged(turbine::Key const key, bool const down) {
if (!down) {
return;
}
@@ -526,14 +525,11 @@ void TileSheetEditorImGui::drawPaletteMenu() noexcept {
}
}
ox::Error TileSheetEditorImGui::updateActiveSubsheet(ox::StringView const&name, int cols, int rows) noexcept {
ox::Error TileSheetEditorImGui::updateActiveSubsheet(
ox::StringView const&name, int const cols, int const rows) noexcept {
return m_model.updateSubsheet(m_model.activeSubSheetIdx(), name, cols, rows);
}
ox::Error TileSheetEditorImGui::setPaletteSelection() noexcept {
return {};
}
void TileSheetEditorImGui::setActiveSubsheet(TileSheet::SubSheetIdx path) noexcept {
m_model.setActiveSubsheet(path);
studio::editConfig<TileSheetEditorConfig>(keelCtx(m_sctx), itemPath(),

View File

@@ -4,7 +4,6 @@
#pragma once
#include <ox/model/def.hpp>
#include <ox/std/vec.hpp>
#include <glutils/glutils.hpp>
@@ -29,7 +28,7 @@ class TileSheetEditorImGui: public studio::Editor {
public:
ox::Signal<ox::Error(ox::StringViewCR name, int cols, int rows)> inputSubmitted;
void show(ox::StringViewCR name, int cols, int rows) noexcept;
void draw(turbine::Context &sctx) noexcept;
void draw(turbine::Context &tctx) noexcept;
void close() noexcept;
[[nodiscard]]
constexpr bool isOpen() const noexcept { return m_show; }
@@ -41,7 +40,7 @@ class TileSheetEditorImGui: public studio::Editor {
public:
ox::Signal<ox::Error(int scale)> inputSubmitted;
void show() noexcept;
void draw(turbine::Context &sctx) noexcept;
void draw(turbine::Context &tctx) noexcept;
void close() noexcept;
[[nodiscard]]
constexpr bool isOpen() const noexcept { return m_show; }
@@ -99,8 +98,6 @@ class TileSheetEditorImGui: public studio::Editor {
ox::Error updateActiveSubsheet(ox::StringView const&name, int cols, int rows) noexcept;
ox::Error setPaletteSelection() noexcept;
// slots
private:
void setActiveSubsheet(TileSheet::SubSheetIdx path) noexcept;

View File

@@ -42,10 +42,10 @@ Palette const TileSheetEditorModel::s_defaultPalette = {
};
TileSheetEditorModel::TileSheetEditorModel(
studio::StudioContext &sctx, ox::StringViewCR path, studio::UndoStack &undoStack):
studio::StudioContext &sctx, ox::StringParam path, studio::UndoStack &undoStack):
m_sctx(sctx),
m_tctx(m_sctx.tctx),
m_path(path),
m_path(std::move(path)),
m_img(*readObj<TileSheet>(keelCtx(m_tctx), m_path).unwrapThrow()),
// ignore failure to load palette
m_pal(readObj<Palette>(keelCtx(m_tctx), m_img.defaultPalette).value),
@@ -53,6 +53,8 @@ TileSheetEditorModel::TileSheetEditorModel(
normalizeSubsheets(m_img.subsheet);
m_pal.updated.connect(this, &TileSheetEditorModel::markUpdated);
m_undoStack.changeTriggered.connect(this, &TileSheetEditorModel::markUpdatedCmdId);
setPalPath();
m_sctx.project->fileMoved.connect(this, &TileSheetEditorModel::handleFileRename);
}
void TileSheetEditorModel::cut() {
@@ -116,18 +118,7 @@ bool TileSheetEditorModel::acceptsClipboardPayload() const noexcept {
}
ox::StringView TileSheetEditorModel::palPath() const noexcept {
auto &path = m_img.defaultPalette;
constexpr ox::StringView uuidPrefix = "uuid://";
if (ox::beginsWith(path, uuidPrefix)) {
auto const uuid = substr(path, uuidPrefix.bytes());
auto const out = keelCtx(m_tctx).uuidToPath.at(uuid);
if (out.error) {
return {};
}
return *out.value;
} else {
return path;
}
return m_palPath;
}
ox::Error TileSheetEditorModel::setPalette(ox::StringViewCR path) noexcept {
@@ -318,10 +309,33 @@ void TileSheetEditorModel::getFillPixels(
}
}
void TileSheetEditorModel::setPalPath() noexcept {
auto &path = m_img.defaultPalette;
constexpr ox::StringView uuidPrefix = "uuid://";
if (ox::beginsWith(path, uuidPrefix)) {
auto const uuid = substr(path, uuidPrefix.bytes());
auto const out = keelCtx(m_tctx).uuidToPath.at(uuid);
if (!out.error) {
m_palPath = *out.value;
}
} else {
m_palPath = path;
}
}
void TileSheetEditorModel::pushCommand(studio::UndoCommand *cmd) noexcept {
std::ignore = m_undoStack.push(ox::UPtr<studio::UndoCommand>{cmd});
m_ongoingDrawCommand = dynamic_cast<DrawCommand*>(cmd);
m_updated = true;
}
ox::Error TileSheetEditorModel::handleFileRename(ox::StringViewCR newPath, ox::UUID const&id) noexcept {
if ((beginsWith(m_img.defaultPalette, "uuid://") &&
substr(m_img.defaultPalette, 7) == id.toString()) ||
m_img.defaultPalette == newPath) {
m_palPath = newPath;
}
return {};
}
}

View File

@@ -25,6 +25,7 @@ class TileSheetEditorModel: public ox::SignalHandler {
studio::StudioContext &m_sctx;
turbine::Context &m_tctx;
ox::String m_path;
ox::String m_palPath;
TileSheet m_img;
TileSheet::SubSheetIdx m_activeSubsSheetIdx;
keel::AssetRef<Palette> m_pal;
@@ -36,7 +37,7 @@ class TileSheetEditorModel: public ox::SignalHandler {
bool m_updated = false;
public:
TileSheetEditorModel(studio::StudioContext &sctx, ox::StringViewCR path, studio::UndoStack &undoStack);
TileSheetEditorModel(studio::StudioContext &sctx, ox::StringParam path, studio::UndoStack &undoStack);
~TileSheetEditorModel() override = default;
@@ -132,8 +133,12 @@ class TileSheetEditorModel: public ox::SignalHandler {
ox::Point const&pt,
int oldColor) const noexcept;
void setPalPath() noexcept;
void pushCommand(studio::UndoCommand *cmd) noexcept;
ox::Error handleFileRename(ox::StringViewCR newPath, ox::UUID const&id) noexcept;
};
constexpr TileSheet const&TileSheetEditorModel::img() const noexcept {

View File

@@ -9,7 +9,6 @@
#endif
#include <ox/event/signal.hpp>
#include <ox/fs/fs.hpp>
#include <ox/model/typenamecatcher.hpp>
#include <ox/std/hashmap.hpp>
#include <ox/std/utility.hpp>
@@ -190,6 +189,8 @@ class AssetManager {
public:
~AssetTypeManagerBase() override = default;
virtual ox::Error updateAssetId(ox::StringViewCR oldId, ox::StringViewCR newId) noexcept = 0;
virtual void gc() noexcept = 0;
};
@@ -202,7 +203,7 @@ class AssetManager {
ox::HashMap<ox::String, ox::UPtr<AssetContainer<T>>> m_cache;
public:
AssetTypeManager(Loader &&loader) noexcept: m_loader(std::move(loader)) {}
explicit AssetTypeManager(Loader &&loader) noexcept: m_loader(std::move(loader)) {}
ox::Result<AssetRef<T>> getAsset(ox::StringViewCR assetId) const noexcept {
OX_REQUIRE(out, m_cache.at(assetId));
@@ -236,6 +237,14 @@ class AssetManager {
return {};
}
ox::Error updateAssetId(ox::StringViewCR oldId, ox::StringViewCR newId) noexcept final {
auto &o = m_cache[oldId];
auto &n = m_cache[newId];
n = std::move(o);
m_cache.erase(oldId);
return {};
}
void gc() noexcept final {
for (auto const&ack : m_cache.keys()) {
auto &ac = m_cache[ack];
@@ -244,10 +253,11 @@ class AssetManager {
}
}
}
};
ox::HashMap<ox::String, ox::UPtr<AssetTypeManagerBase>> m_assetTypeManagers;
ox::HashMap<ox::String, ox::Signal<ox::Error(ox::StringViewCR assetId)>> m_fileUpdated;
ox::HashMap<ox::String, ox::UPtr<ox::Signal<ox::Error(ox::StringViewCR assetId)>>> m_fileUpdated;
template<typename T>
ox::Result<AssetTypeManager<T>*> getTypeManager() noexcept {
@@ -279,7 +289,7 @@ class AssetManager {
}
ox::Error reloadAsset(ox::StringViewCR assetId) noexcept {
m_fileUpdated[assetId].emit(assetId);
m_fileUpdated[assetId]->emit(assetId);
return {};
}
@@ -287,15 +297,40 @@ class AssetManager {
ox::Result<AssetRef<T>> loadAsset(ox::StringViewCR assetId) noexcept {
OX_REQUIRE(m, getTypeManager<T>());
OX_REQUIRE(out, m->loadAsset(assetId));
m_fileUpdated[assetId].connect(m, &AssetTypeManager<T>::reloadAsset);
if (!m_fileUpdated.contains(assetId)) [[unlikely]] {
m_fileUpdated[assetId] = ox::make_unique<ox::Signal<ox::Error(ox::StringViewCR assetId)>>();
}
m_fileUpdated[assetId]->connect(m, &AssetTypeManager<T>::reloadAsset);
return out;
}
ox::Error updateAssetId(ox::StringViewCR oldId, ox::StringViewCR newId) noexcept {
gc();
if (m_fileUpdated.contains(newId)) {
return ox::Error{1, "new asset ID already has an entry"};
}
auto &o = m_fileUpdated[oldId];
auto &n = m_fileUpdated[newId];
n = std::move(o);
m_fileUpdated.erase(oldId);
for (auto &k : m_assetTypeManagers.keys()) {
auto const &tm = m_assetTypeManagers[k];
std::ignore = tm->updateAssetId(oldId, newId);
}
return {};
}
void gc() noexcept {
for (auto const&amk : m_assetTypeManagers.keys()) {
auto &am = m_assetTypeManagers[amk];
am->gc();
}
for (auto const&k : m_fileUpdated.keys()) {
auto &s = m_fileUpdated[k];
if (s->connectionCnt() == 0) {
m_fileUpdated.erase(k);
}
}
}
};
#else

View File

@@ -47,6 +47,8 @@ ox::Result<ox::CStringView> getPath(Context &ctx, ox::FileAddress const&fileAddr
ox::Result<ox::CStringView> getPath(Context &ctx, ox::CStringViewCR fileId) noexcept;
ox::Error updatePath(Context &ctx, ox::StringViewCR oldPath, ox::StringViewCR newPath) noexcept;
constexpr ox::Result<ox::UUID> uuidUrlToUuid(ox::StringViewCR uuidUrl) noexcept {
return ox::UUID::fromString(substr(uuidUrl, 7));
}

View File

@@ -129,6 +129,21 @@ ox::Result<ox::CStringView> getPath(Context &ctx, ox::CStringViewCR fileId) noex
}
}
ox::Error updatePath(Context &ctx, ox::StringViewCR oldPath, ox::StringViewCR newPath) noexcept {
#ifndef OX_BARE_METAL
if (auto const r = ctx.pathToUuid.at(oldPath); r.ok()) {
auto const ustr = r.value->toString();
ctx.pathToUuid[newPath] = *r.value;
ctx.pathToUuid.erase(oldPath);
ctx.uuidToPath[ustr] = newPath;
return {};
}
return ctx.assetManager.updateAssetId(oldPath, newPath);
#else
return ox::Error(1, "updating path is not supported on this platform");
#endif
}
ox::Result<ox::CStringView> uuidUrlToPath(Context &ctx, ox::StringViewCR uuid) noexcept {
#ifndef OX_BARE_METAL
OX_REQUIRE_M(out, ctx.uuidToPath.at(substr(uuid, 7)));

View File

@@ -39,10 +39,11 @@ static ox::Error pathToInode(
auto const uuid = ox::substr(path, 7);
OX_RETURN_ERROR(keel::uuidToPath(ctx, uuid).to<ox::String>().moveTo(path));
}
OX_REQUIRE(s, dest.stat(path));
auto const s = dest.stat(path);
auto const inode = s.ok() ? s.value.inode : 0;
OX_RETURN_ERROR(typeVal->set(static_cast<int8_t>(ox::FileAddressType::Inode)));
oxOutf("\tpath to inode: {} => {}\n", path, s.inode);
return data.set(2, s.inode);
oxOutf("\tpath to inode: {} => {}\n", path, inode);
return data.set(2, inode);
}
static ox::Error transformFileAddressesObj(

View File

@@ -9,6 +9,7 @@ add_library(
newmenu.cpp
newproject.cpp
projectexplorer.cpp
renamefile.cpp
studioapp.cpp
)
target_compile_definitions(

View File

@@ -31,6 +31,9 @@ void ProjectExplorer::fileContextMenu(ox::StringViewCR path) const noexcept {
if (ImGui::MenuItem("Delete")) {
deleteItem.emit(path);
}
if (ImGui::MenuItem("Rename")) {
renameItem.emit(path);
}
ImGui::EndPopup();
}
}

View File

@@ -19,6 +19,7 @@ class ProjectExplorer final: public FileExplorer {
ox::Signal<ox::Error(ox::StringViewCR)> addItem;
ox::Signal<ox::Error(ox::StringViewCR)> addDir;
ox::Signal<ox::Error(ox::StringViewCR)> deleteItem;
ox::Signal<ox::Error(ox::StringViewCR)> renameItem;
explicit ProjectExplorer(keel::Context &kctx) noexcept;

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <studio/imguiutil.hpp>
#include "renamefile.hpp"
namespace studio {
RenameFile::RenameFile() {
setTitle("Rename File");
}
ox::Error RenameFile::openPath(ox::StringParam path) noexcept {
m_oldPath = std::move(path);
OX_REQUIRE(idx, ox::findIdx(m_oldPath.rbegin(), m_oldPath.rend(), '/'));
m_name = substr(m_oldPath, idx + 1);
m_path = substr(m_oldPath, 0, idx + 1);
open();
return {};
}
void RenameFile::open() noexcept {
m_stage = Stage::Opening;
}
void RenameFile::close() noexcept {
m_stage = Stage::Closed;
m_open = false;
}
bool RenameFile::isOpen() const noexcept {
return m_open;
}
void RenameFile::draw(StudioContext &ctx) noexcept {
switch (m_stage) {
case Stage::Closed:
break;
case Stage::Opening:
ImGui::OpenPopup(title().c_str());
m_open = true;
m_stage = Stage::Open;
[[fallthrough]];
case Stage::Open:
setSize({250, 0});
drawWindow(ctx.tctx, m_open, [this] {
if (ImGui::IsWindowAppearing()) {
ImGui::SetKeyboardFocusHere();
}
ig::InputText("Name", m_name);
ImGui::Text("%s%s", m_path.c_str(), m_name.c_str());
if (ig::PopupControlsOkCancel(m_open) == ig::PopupResponse::OK) {
moveFile.emit(m_oldPath, m_path + m_name);
close();
}
});
break;
}
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <studio/context.hpp>
#include <studio/popup.hpp>
namespace studio {
class RenameFile: public Popup {
private:
enum class Stage {
Closed,
Opening,
Open,
};
Stage m_stage{};
ox::String m_oldPath;
ox::String m_path;
ox::IString<255> m_name;
bool m_open{};
public:
ox::Signal<ox::Error(ox::StringViewCR src, ox::StringViewCR dst)> moveFile;
RenameFile();
ox::Error openPath(ox::StringParam path) noexcept;
void open() noexcept override;
void close() noexcept override;
[[nodiscard]]
bool isOpen() const noexcept override;
void draw(StudioContext &ctx) noexcept override;
};
}

View File

@@ -47,20 +47,20 @@ OX_MODEL_BEGIN(StudioConfig)
OX_MODEL_END()
StudioUI::StudioUI(turbine::Context &ctx, ox::StringParam projectDataDir) noexcept:
m_sctx(*this, ctx),
m_tctx(ctx),
m_projectDataDir(std::move(projectDataDir)),
m_projectExplorer(keelCtx(m_tctx)),
m_newProject(m_projectDataDir),
m_aboutPopup(m_tctx) {
m_sctx{*this, ctx},
m_tctx{ctx},
m_projectDataDir{std::move(projectDataDir)},
m_projectExplorer{keelCtx(m_tctx)},
m_newProject{m_projectDataDir},
m_aboutPopup{m_tctx} {
turbine::setApplicationData(m_tctx, &m_sctx);
m_projectExplorer.fileChosen.connect(this, &StudioUI::openFile);
m_projectExplorer.addDir.connect(this, &StudioUI::addDir);
m_projectExplorer.addItem.connect(this, &StudioUI::addFile);
m_projectExplorer.deleteItem.connect(this, &StudioUI::deleteFile);
m_projectExplorer.renameItem.connect(this, &StudioUI::renameFile);
m_newProject.finished.connect(this, &StudioUI::createOpenProject);
m_newMenu.finished.connect(this, &StudioUI::openFile);
ImGui::GetIO().IniFilename = nullptr;
loadModules();
// open project and files
auto const [config, err] = studio::readConfig<StudioConfig>(keelCtx(m_tctx));
@@ -369,6 +369,14 @@ ox::Error StudioUI::deleteFile(ox::StringViewCR path) noexcept {
return {};
}
ox::Error StudioUI::renameFile(ox::StringViewCR path) noexcept {
return m_renameFile.openPath(path);
}
ox::Error StudioUI::handleMoveFile(ox::StringViewCR, ox::UUID const&) noexcept {
return m_projectExplorer.refreshProjectTreeModel();
}
ox::Error StudioUI::createOpenProject(ox::StringViewCR path) noexcept {
std::error_code ec;
std::filesystem::create_directories(toStdStringView(path), ec);
@@ -386,10 +394,12 @@ ox::Error StudioUI::openProjectPath(ox::StringParam path) noexcept {
m_sctx.project = m_project.get();
turbine::setWindowTitle(m_tctx, ox::sfmt("{} - {}", keelCtx(m_tctx).appName, m_project->projectPath()));
m_deleteConfirmation.deleteFile.connect(m_sctx.project, &Project::deleteItem);
m_renameFile.moveFile.connect(m_project.get(), &Project::moveItem);
m_newDirDialog.newDir.connect(m_sctx.project, &Project::mkdir);
m_project->dirAdded.connect(&m_projectExplorer, &ProjectExplorer::refreshProjectTreeModel);
m_project->fileAdded.connect(&m_projectExplorer, &ProjectExplorer::refreshProjectTreeModel);
m_project->fileDeleted.connect(&m_projectExplorer, &ProjectExplorer::refreshProjectTreeModel);
m_project->fileMoved.connect(this, &StudioUI::handleMoveFile);
m_openFiles.clear();
m_editors.clear();
studio::editConfig<StudioConfig>(keelCtx(m_tctx), [&](StudioConfig &config) {

View File

@@ -19,6 +19,7 @@
#include "newmenu.hpp"
#include "newproject.hpp"
#include "projectexplorer.hpp"
#include "renamefile.hpp"
namespace studio {
@@ -42,14 +43,16 @@ class StudioUI: public ox::SignalHandler {
NewMenu m_newMenu{keelCtx(m_tctx)};
DeleteConfirmation m_deleteConfirmation;
NewDir m_newDirDialog;
RenameFile m_renameFile;
NewProject m_newProject;
AboutPopup m_aboutPopup;
ox::Array<Popup*, 5> const m_popups = {
ox::Array<Popup*, 6> const m_popups = {
&m_newMenu,
&m_newProject,
&m_aboutPopup,
&m_deleteConfirmation,
&m_newDirDialog,
&m_renameFile,
};
bool m_showProjectExplorer = true;
@@ -95,6 +98,10 @@ class StudioUI: public ox::SignalHandler {
ox::Error deleteFile(ox::StringViewCR path) noexcept;
ox::Error renameFile(ox::StringViewCR path) noexcept;
ox::Error handleMoveFile(ox::StringViewCR path, ox::UUID const&id) noexcept;
ox::Error createOpenProject(ox::StringViewCR path) noexcept;
ox::Error openProjectPath(ox::StringParam path) noexcept;

View File

@@ -8,7 +8,6 @@
#include <ox/claw/write.hpp>
#include <ox/event/signal.hpp>
#include <ox/fs/fs.hpp>
#include <ox/mc/mc.hpp>
#include <ox/model/descwrite.hpp>
#include <ox/std/hashmap.hpp>
@@ -26,6 +25,7 @@ enum class ProjectEvent {
FileRecognized,
FileDeleted,
FileUpdated,
FileMoved,
};
[[nodiscard]]
@@ -49,7 +49,7 @@ constexpr ox::StringView parentDir(ox::StringView path) noexcept {
class Project: public ox::SignalHandler {
private:
ox::SmallMap<ox::String, ox::Optional<ox::ClawFormat>> m_typeFmt;
keel::Context &m_ctx;
keel::Context &m_kctx;
ox::String m_path;
ox::String m_projectDataDir;
ox::String m_typeDescPath;
@@ -92,6 +92,8 @@ class Project: public ox::SignalHandler {
ox::Result<ox::FileStat> stat(ox::StringViewCR path) const noexcept;
ox::Error moveItem(ox::StringViewCR src, ox::StringViewCR dest) noexcept;
ox::Error deleteItem(ox::StringViewCR path) noexcept;
[[nodiscard]]
@@ -114,7 +116,7 @@ class Project: public ox::SignalHandler {
ox::Result<ox::Buffer> loadBuff(ox::StringViewCR path) const noexcept;
ox::Error lsProcDir(ox::Vector<ox::String> *paths, ox::StringViewCR path) const noexcept;
ox::Error lsProcDir(ox::Vector<ox::String> &paths, ox::StringViewCR path) const noexcept;
ox::Result<ox::Vector<ox::String>> listFiles(ox::StringViewCR path = "") const noexcept;
@@ -128,7 +130,8 @@ class Project: public ox::SignalHandler {
// file.
ox::Signal<ox::Error(ox::StringViewCR)> fileRecognized;
ox::Signal<ox::Error(ox::StringViewCR)> fileDeleted;
ox::Signal<ox::Error(ox::StringView, ox::UUID)> fileUpdated;
ox::Signal<ox::Error(ox::StringViewCR path, ox::UUID const&id)> fileUpdated;
ox::Signal<ox::Error(ox::StringViewCR newPath, ox::UUID const&id)> fileMoved;
};
@@ -151,8 +154,8 @@ ox::Error Project::writeObj(ox::StringViewCR path, T const&obj, ox::ClawFormat f
if (!descExists) {
OX_RETURN_ERROR(writeTypeStore());
}
OX_RETURN_ERROR(keel::reloadAsset(m_ctx, path));
OX_REQUIRE(uuid, pathToUuid(m_ctx, path));
OX_RETURN_ERROR(keel::reloadAsset(m_kctx, path));
OX_REQUIRE(uuid, pathToUuid(m_kctx, path));
fileUpdated.emit(path, uuid);
return {};
}
@@ -200,6 +203,9 @@ ox::Error Project::subscribe(ProjectEvent e, ox::SignalHandler *tgt, Functor &&s
case ProjectEvent::FileUpdated:
connect(this, &Project::fileUpdated, tgt, slot);
break;
case ProjectEvent::FileMoved:
connect(this, &Project::fileMoved, tgt, slot);
break;
}
return {};
}

View File

@@ -26,13 +26,14 @@ static void generateTypes(ox::TypeStore &ts) noexcept {
}
}
Project::Project(keel::Context &ctx, ox::String path, ox::StringViewCR projectDataDir):
m_ctx(ctx),
m_kctx(ctx),
m_path(std::move(path)),
m_projectDataDir(projectDataDir),
m_typeDescPath(ox::sfmt("/{}/type_descriptors", m_projectDataDir)),
m_typeStore(*m_ctx.rom, ox::sfmt("/{}/type_descriptors", projectDataDir)),
m_fs(*m_ctx.rom) {
m_typeStore(*m_kctx.rom, ox::sfmt("/{}/type_descriptors", projectDataDir)),
m_fs(*m_kctx.rom) {
oxTracef("studio", "Project: {}", m_path);
generateTypes(m_typeStore);
if constexpr(ox::defines::Debug) {
@@ -69,6 +70,14 @@ ox::Result<ox::FileStat> Project::stat(ox::StringViewCR path) const noexcept {
return m_fs.stat(path);
}
ox::Error Project::moveItem(ox::StringViewCR src, ox::StringViewCR dest) noexcept {
OX_RETURN_ERROR(m_fs.move(src, dest));
OX_RETURN_ERROR(keel::updatePath(m_kctx, src, dest));
OX_REQUIRE(uuid, keel::pathToUuid(m_kctx, dest));
fileMoved.emit(dest, uuid);
return {};
}
ox::Error Project::deleteItem(ox::StringViewCR path) noexcept {
OX_REQUIRE(stat, m_fs.stat(path));
if (stat.fileType == ox::FileType::Directory) {
@@ -142,7 +151,7 @@ ox::Error Project::writeBuff(ox::StringViewCR path, ox::BufferView const&buff) n
ox::Buffer outBuff;
outBuff.reserve(buff.size() + HdrSz);
ox::BufferWriter writer(&outBuff);
auto const [uuid, err] = pathToUuid(m_ctx, path);
auto const [uuid, err] = pathToUuid(m_kctx, path);
if (!err) {
OX_RETURN_ERROR(keel::writeUuidHeader(writer, uuid));
}
@@ -162,14 +171,14 @@ ox::Result<ox::Buffer> Project::loadBuff(ox::StringViewCR path) const noexcept {
return m_fs.read(path);
}
ox::Error Project::lsProcDir(ox::Vector<ox::String> *paths, ox::StringViewCR path) const noexcept {
ox::Error Project::lsProcDir(ox::Vector<ox::String> &paths, ox::StringViewCR path) const noexcept {
OX_REQUIRE(files, m_fs.ls(path));
for (auto const&name : files) {
auto fullPath = ox::sfmt("{}/{}", path, name);
OX_REQUIRE(stat, m_fs.stat(ox::StringView(fullPath)));
switch (stat.fileType) {
case ox::FileType::NormalFile:
paths->emplace_back(std::move(fullPath));
paths.emplace_back(std::move(fullPath));
break;
case ox::FileType::Directory:
OX_RETURN_ERROR(lsProcDir(paths, fullPath));
@@ -183,7 +192,7 @@ ox::Error Project::lsProcDir(ox::Vector<ox::String> *paths, ox::StringViewCR pat
ox::Result<ox::Vector<ox::String>> Project::listFiles(ox::StringViewCR path) const noexcept {
ox::Vector<ox::String> paths;
OX_RETURN_ERROR(lsProcDir(&paths, path));
OX_RETURN_ERROR(lsProcDir(paths, path));
return paths;
}

View File

@@ -222,6 +222,7 @@ ox::Error initGfx(Context &ctx) noexcept {
//io.MouseDrawCursor = true;
ImGui_ImplGlfw_InitForOpenGL(ctx.window, true);
ImGui_ImplOpenGL3_Init();
io.IniFilename = nullptr;
themeImgui();
#endif
return {};