From 7d53028faf67ddcfa75a9ad1e59d33d0c5673c3c Mon Sep 17 00:00:00 2001 From: Gary Talent Date: Fri, 24 Jan 2025 00:02:59 -0600 Subject: [PATCH 01/14] [studio] Cleanup --- src/olympic/studio/applib/src/projectexplorer.hpp | 1 - src/olympic/studio/modlib/src/project.cpp | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/olympic/studio/applib/src/projectexplorer.hpp b/src/olympic/studio/applib/src/projectexplorer.hpp index 922fe42f..400155c7 100644 --- a/src/olympic/studio/applib/src/projectexplorer.hpp +++ b/src/olympic/studio/applib/src/projectexplorer.hpp @@ -5,7 +5,6 @@ #pragma once #include -#include #include #include diff --git a/src/olympic/studio/modlib/src/project.cpp b/src/olympic/studio/modlib/src/project.cpp index cd90d8a0..9548975e 100644 --- a/src/olympic/studio/modlib/src/project.cpp +++ b/src/olympic/studio/modlib/src/project.cpp @@ -35,7 +35,7 @@ Project::Project(keel::Context &ctx, ox::String path, ox::StringViewCR projectDa m_fs(*m_ctx.rom) { oxTracef("studio", "Project: {}", m_path); generateTypes(m_typeStore); - if (ox::defines::Debug) { + if constexpr(ox::defines::Debug) { OX_THROW_ERROR(writeTypeStore()); } buildFileIndex(); From 695e7a4561e2dbffaeaa6c41b01c12713f753161 Mon Sep 17 00:00:00 2001 From: Gary Talent Date: Fri, 24 Jan 2025 23:19:09 -0600 Subject: [PATCH 02/14] [nostalgia/gfx/studio/paletteeditor] Change move color mechanism to use drag/drop --- release-notes.md | 5 ++- .../commands/movecolorcommand.cpp | 21 +++++++--- .../paletteeditor/paletteeditor-imgui.cpp | 39 +++++++++---------- 3 files changed, 38 insertions(+), 27 deletions(-) diff --git a/release-notes.md b/release-notes.md index 783ce3e6..57449ad4 100644 --- a/release-notes.md +++ b/release-notes.md @@ -4,10 +4,11 @@ * Add PaletteV5 to accommodate namespace change. * Add TileSheetV5. TileSheetV5 retains the bpp field for the sake of CompactTileSheet, but always store it pixel as 8 bpp for itself. -* Replace file picker combo boxes with support for dragging files from the - project explorer. +* Replace file picker combo boxes with a browse button and file picker, and + support for dragging files from the project explorer. * Add ability to create directories. * Add ability to add files to specific directories. * Add ability to delete files from the project explorer. * Ctrl- keyboard shortcuts for jumping between tabs. * Fix Palette Editor to ignore keyboard input when popups are open. +* Palette Editor move color mechanism now uses drag and drop. diff --git a/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/movecolorcommand.cpp b/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/movecolorcommand.cpp index 282790f6..6b56a984 100644 --- a/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/movecolorcommand.cpp +++ b/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/movecolorcommand.cpp @@ -9,7 +9,10 @@ namespace nostalgia::gfx { MoveColorCommand::MoveColorCommand( - Palette &pal, size_t page, size_t srcIdx, size_t dstIdx) noexcept: + Palette &pal, + size_t const page, + size_t const srcIdx, + size_t const dstIdx) noexcept: m_pal(pal), m_page(page), m_srcIdx(srcIdx), @@ -29,10 +32,18 @@ ox::Error MoveColorCommand::undo() noexcept { return {}; } -void MoveColorCommand::moveColor(size_t srcIdx, size_t dstIdx) noexcept { - auto const c = color(m_pal, m_page, srcIdx); - std::ignore = colors(m_pal, m_page).erase(srcIdx); - colors(m_pal, m_page).emplace(dstIdx, c); +void MoveColorCommand::moveColor( + size_t const srcIdx, size_t const dstIdx) noexcept { + { + auto const c = color(m_pal, m_page, srcIdx); + std::ignore = colors(m_pal, m_page).erase(srcIdx); + colors(m_pal, m_page).emplace(dstIdx, c); + } + { + auto name = std::move(m_pal.colorNames[srcIdx]); + std::ignore = m_pal.colorNames.erase(srcIdx); + m_pal.colorNames.emplace(dstIdx, std::move(name)); + } } } diff --git a/src/nostalgia/modules/gfx/src/studio/paletteeditor/paletteeditor-imgui.cpp b/src/nostalgia/modules/gfx/src/studio/paletteeditor/paletteeditor-imgui.cpp index 35e4b1e3..8ca65c23 100644 --- a/src/nostalgia/modules/gfx/src/studio/paletteeditor/paletteeditor-imgui.cpp +++ b/src/nostalgia/modules/gfx/src/studio/paletteeditor/paletteeditor-imgui.cpp @@ -23,6 +23,15 @@ namespace nostalgia::gfx { namespace ig = studio::ig; +struct ColorDragDrop { + static constexpr auto TypeName = "nostalgia.gfx.ColorDragDrop"; + static constexpr auto TypeVersion = 1; + uint32_t i{}; +}; + +OX_MODEL_BEGIN(ColorDragDrop) + OX_MODEL_FIELD(i) +OX_MODEL_END() void PaletteEditorImGui::PageRenameDialog::draw(turbine::Context &tctx) noexcept { if (!m_show) { @@ -125,26 +134,6 @@ void PaletteEditorImGui::drawColorsEditor() noexcept { m_selectedColorRow = ox::min(colorCnt(m_pal, m_page) - 1, m_selectedColorRow); colorEditor = m_selectedColorRow < colorCnt(m_pal, m_page); } - ImGui::SameLine(); - ImGui::BeginDisabled(m_selectedColorRow <= 0); - { - if (ImGui::Button("Move Up", sz)) { - std::ignore = pushCommand( - m_pal, m_page, m_selectedColorRow, m_selectedColorRow - 1); - --m_selectedColorRow; - } - } - ImGui::EndDisabled(); - ImGui::SameLine(); - ImGui::BeginDisabled(m_selectedColorRow >= colorCnt(m_pal, m_page) - 1); - { - if (ImGui::Button("Move Down", sz)) { - std::ignore = pushCommand( - m_pal, m_page, m_selectedColorRow, m_selectedColorRow + 1); - ++m_selectedColorRow; - } - } - ImGui::EndDisabled(); } ImGui::EndDisabled(); } @@ -179,6 +168,16 @@ void PaletteEditorImGui::drawColorsEditor() noexcept { "##ColorRow", i == m_selectedColorRow, ImGuiSelectableFlags_SpanAllColumns)) { m_selectedColorRow = i; } + std::ignore = ig::dragDropSource([this, i] { + ImGui::Text("%s", m_pal.colorNames[i].c_str()); + return ig::setDragDropPayload(ColorDragDrop{i}); + }, ImGuiDragDropFlags_SourceAllowNullID); + if (ig::DragDropTarget const d; d) { + auto const [src, err] = ig::getDragDropPayload(); + if (!err) { + std::ignore = pushCommand(m_pal, m_page, src.i, i); + } + } ++i; } } From 04ad0f02640b65d8888f625ca51bc6c8046f3fdc Mon Sep 17 00:00:00 2001 From: Gary Talent Date: Fri, 24 Jan 2025 23:26:30 -0600 Subject: [PATCH 03/14] [studio] Add drag/drop functions that use model TypeName for name --- .../modlib/include/studio/imguiutil.hpp | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/olympic/studio/modlib/include/studio/imguiutil.hpp b/src/olympic/studio/modlib/include/studio/imguiutil.hpp index e9a4d1d7..0f71da88 100644 --- a/src/olympic/studio/modlib/include/studio/imguiutil.hpp +++ b/src/olympic/studio/modlib/include/studio/imguiutil.hpp @@ -34,19 +34,37 @@ ox::Result getDragDropPayload(ox::CStringViewCR name) noexcept { static_cast(payload->DataSize)}); } +template +ox::Result getDragDropPayload() noexcept { + auto const payload = ImGui::AcceptDragDropPayload(ox::ModelTypeName_v); + if (!payload) { + return ox::Error(1, "No drag/drop payload"); + } + return ox::readClaw({ + reinterpret_cast(payload->Data), + static_cast(payload->DataSize)}); +} + 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 +ox::Error setDragDropPayload(T const&obj) noexcept { + OX_REQUIRE(buff, ox::writeClaw(obj, ox::ClawFormat::Metal)); + ImGui::SetDragDropPayload(ox::ModelTypeName_v, buff.data(), buff.size()); + return {}; +} + class DragDropSource { private: bool const m_active{}; public: - DragDropSource() noexcept: - m_active(ImGui::BeginDragDropSource()) { + DragDropSource(ImGuiDragDropFlags const flags = 0) noexcept: + m_active(ImGui::BeginDragDropSource(flags)) { } ~DragDropSource() noexcept { if (m_active) { @@ -58,14 +76,14 @@ class DragDropSource { } }; -auto dragDropSource(auto const&cb) noexcept { +auto dragDropSource(auto const&cb, ImGuiDragDropFlags const flags = 0) noexcept { if constexpr(ox::is_same_v) { - if (ig::DragDropSource const tgt; tgt) [[unlikely]] { + if (ig::DragDropSource const tgt{flags}; tgt) [[unlikely]] { return cb(); } return ox::Error{}; } else { - if (ig::DragDropSource const tgt; tgt) [[unlikely]] { + if (ig::DragDropSource const tgt{flags}; tgt) [[unlikely]] { cb(); } } From 098c8cb844bd404a6119520c1320a0a60bba6b54 Mon Sep 17 00:00:00 2001 From: Gary Talent Date: Fri, 24 Jan 2025 23:46:26 -0600 Subject: [PATCH 04/14] [nostalgia/gfx/studio] Make move color commands affect all pages --- .../studio/paletteeditor/commands/movecolorcommand.cpp | 10 ++++------ .../studio/paletteeditor/commands/movecolorcommand.hpp | 3 +-- .../src/studio/paletteeditor/paletteeditor-imgui.cpp | 2 +- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/movecolorcommand.cpp b/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/movecolorcommand.cpp index 6b56a984..ffd2dba1 100644 --- a/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/movecolorcommand.cpp +++ b/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/movecolorcommand.cpp @@ -10,11 +10,9 @@ namespace nostalgia::gfx { MoveColorCommand::MoveColorCommand( Palette &pal, - size_t const page, size_t const srcIdx, size_t const dstIdx) noexcept: m_pal(pal), - m_page(page), m_srcIdx(srcIdx), m_dstIdx(dstIdx) {} @@ -34,10 +32,10 @@ ox::Error MoveColorCommand::undo() noexcept { void MoveColorCommand::moveColor( size_t const srcIdx, size_t const dstIdx) noexcept { - { - auto const c = color(m_pal, m_page, srcIdx); - std::ignore = colors(m_pal, m_page).erase(srcIdx); - colors(m_pal, m_page).emplace(dstIdx, c); + for (size_t page{}; page < m_pal.pages.size(); ++page) { + auto const c = color(m_pal, page, srcIdx); + std::ignore = colors(m_pal, page).erase(srcIdx); + colors(m_pal, page).emplace(dstIdx, c); } { auto name = std::move(m_pal.colorNames[srcIdx]); diff --git a/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/movecolorcommand.hpp b/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/movecolorcommand.hpp index 7858ef6f..d1d8f21c 100644 --- a/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/movecolorcommand.hpp +++ b/src/nostalgia/modules/gfx/src/studio/paletteeditor/commands/movecolorcommand.hpp @@ -13,12 +13,11 @@ namespace nostalgia::gfx { class MoveColorCommand: public studio::UndoCommand { private: Palette &m_pal; - size_t const m_page = 0; std::size_t const m_srcIdx = 0; std::size_t const m_dstIdx = 0; public: - MoveColorCommand(Palette &pal, size_t page, size_t srcIdx, size_t dstIdx) noexcept; + MoveColorCommand(Palette &pal, size_t srcIdx, size_t dstIdx) noexcept; ~MoveColorCommand() noexcept override = default; diff --git a/src/nostalgia/modules/gfx/src/studio/paletteeditor/paletteeditor-imgui.cpp b/src/nostalgia/modules/gfx/src/studio/paletteeditor/paletteeditor-imgui.cpp index 8ca65c23..9cdf967c 100644 --- a/src/nostalgia/modules/gfx/src/studio/paletteeditor/paletteeditor-imgui.cpp +++ b/src/nostalgia/modules/gfx/src/studio/paletteeditor/paletteeditor-imgui.cpp @@ -175,7 +175,7 @@ void PaletteEditorImGui::drawColorsEditor() noexcept { if (ig::DragDropTarget const d; d) { auto const [src, err] = ig::getDragDropPayload(); if (!err) { - std::ignore = pushCommand(m_pal, m_page, src.i, i); + std::ignore = pushCommand(m_pal, src.i, i); } } ++i; From f01d303381ad74f1ba3400fc3a03248396eaae41 Mon Sep 17 00:00:00 2001 From: Gary Talent Date: Sat, 25 Jan 2025 20:13:47 -0600 Subject: [PATCH 05/14] [ox/std] Fix UPtr compare with nullptr --- deps/ox/src/ox/std/memory.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deps/ox/src/ox/std/memory.hpp b/deps/ox/src/ox/std/memory.hpp index 1686c526..f5f3f9fb 100644 --- a/deps/ox/src/ox/std/memory.hpp +++ b/deps/ox/src/ox/std/memory.hpp @@ -260,12 +260,12 @@ constexpr bool operator==(const UniquePtr &p1, const UniquePtr &p2) noexce template constexpr bool operator==(const UniquePtr &p1, std::nullptr_t) noexcept { - return p1.get(); + return p1.get() == nullptr; } template constexpr bool operator==(std::nullptr_t, const UniquePtr &p2) noexcept { - return p2.get(); + return p2.get() == nullptr; } From 5145595d574bd82a09f4944be0e94d647fb2e4dc Mon Sep 17 00:00:00 2001 From: Gary Talent Date: Sat, 25 Jan 2025 22:16:42 -0600 Subject: [PATCH 06/14] [ox/std] Fix HashMap collision handling --- deps/ox/src/ox/std/hashmap.hpp | 132 ++++++++++++++++-------------- deps/ox/src/ox/std/test/tests.cpp | 10 +++ 2 files changed, 80 insertions(+), 62 deletions(-) diff --git a/deps/ox/src/ox/std/hashmap.hpp b/deps/ox/src/ox/std/hashmap.hpp index f2fbb599..14a53927 100644 --- a/deps/ox/src/ox/std/hashmap.hpp +++ b/deps/ox/src/ox/std/hashmap.hpp @@ -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 next; K key = {}; T value{}; }; Vector m_keys; - Vector m_pairs; + Vector> m_pairs; public: explicit constexpr HashMap(std::size_t size = 127); @@ -73,10 +75,10 @@ class HashMap { constexpr void expand(); template - constexpr Pair *const&access(Vector const&pairs, KK const&key) const; + constexpr UPtr const &access(Vector> const &pairs, KK const &key) const; template - constexpr Pair *&access(Vector &pairs, KK const&key); + constexpr UPtr &access(Vector> &pairs, KK const &key); }; @@ -85,14 +87,13 @@ constexpr HashMap::HashMap(std::size_t size): m_pairs(size) { } template -constexpr HashMap::HashMap(HashMap const&other) { - m_pairs = other.m_pairs; +constexpr HashMap::HashMap(HashMap const &other) { + operator=(other); } template -constexpr HashMap::HashMap(HashMap &&other) noexcept { - m_keys = std::move(other.m_keys); - m_pairs = std::move(other.m_pairs); +constexpr HashMap::HashMap(HashMap &&other) noexcept { + operator=(std::move(other)); } template @@ -101,7 +102,7 @@ constexpr HashMap::~HashMap() { } template -constexpr bool HashMap::operator==(HashMap const&other) const { +constexpr bool HashMap::operator==(HashMap const &other) const { if (m_keys != other.m_keys) { return false; } @@ -115,19 +116,25 @@ constexpr bool HashMap::operator==(HashMap const&other) const { } template -constexpr HashMap &HashMap::operator=(HashMap const&other) { +constexpr HashMap &HashMap::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(); + dst->key = src->key; + dst->value = src->value; + } } return *this; } template -constexpr HashMap &HashMap::operator=(HashMap &&other) noexcept { +constexpr HashMap &HashMap::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 &HashMap::operator=(HashMap &&other) noexcep } template -constexpr T &HashMap::operator[](MaybeView_t const&k) { - auto p = &access(m_pairs, k); +constexpr T &HashMap::operator[](MaybeView_t const &key) { + auto p = &access(m_pairs, key); if (*p == nullptr) { if (static_cast(m_pairs.size()) * 0.7 < static_cast(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(); + (*p)->key = key; + m_keys.emplace_back(key); } return (*p)->value; } template -constexpr Result HashMap::at(MaybeView_t const&k) noexcept { - auto p = access(m_pairs, k); +constexpr Result HashMap::at(MaybeView_t 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 -constexpr Result HashMap::at(MaybeView_t const&k) const noexcept { - auto p = access(m_pairs, k); +constexpr Result HashMap::at(MaybeView_t 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 -constexpr void HashMap::erase(MaybeView_t const&k) { - if (!contains(k)) { +constexpr void HashMap::erase(MaybeView_t const &key) { + if (!contains(key)) { return; } - auto h = ox::hash>{}(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>{}(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 -constexpr bool HashMap::contains(MaybeView_t const&k) const noexcept { - return access(m_pairs, k) != nullptr; +constexpr bool HashMap::contains(MaybeView_t const &key) const noexcept { + return access(m_pairs, key).get() != nullptr; } template @@ -204,27 +203,26 @@ constexpr Vector const&HashMap::keys() const noexcept { template constexpr Vector HashMap::values() const noexcept { Vector out; - out.reserve(m_pairs.size()); - for (auto const&p : m_pairs) { - out.emplace_back(p->value); + out.reserve(m_keys.size()); + for (auto const &p : m_pairs) { + if (out) { + out.emplace_back(p->value); + } } return out; } template constexpr void HashMap::clear() { - for (std::size_t i = 0; i < m_pairs.size(); i++) { - delete m_pairs[i]; - } m_pairs.clear(); m_pairs.resize(127); } template constexpr void HashMap::expand() { - Vector r(m_pairs.size() * 2); + Vector> r{m_pairs.size() * 2}; for (std::size_t i = 0; i < m_keys.size(); ++i) { - auto const&k = m_keys[i]; + auto const &k = m_keys[i]; access(r, k) = std::move(access(m_pairs, k)); } m_pairs = std::move(r); @@ -232,29 +230,39 @@ constexpr void HashMap::expand() { template template -constexpr typename HashMap::Pair *const&HashMap::access(Vector const&pairs, KK const&k) const { - auto h = static_cast(ox::hash{}(k) % pairs.size()); +constexpr UPtr::Pair> const &HashMap::access( + Vector> const& pairs, + KK const &key) const { + auto const h = static_cast(ox::hash{}(key) % pairs.size()); + auto const &p = *pairs.at(h).unwrap(); + if (p == nullptr || p->key == key) { + return p; + } + auto c = &p->next; while (true) { - auto const&p = *pairs.at(h).unwrap(); - if (p == nullptr || p->key == k) { - return p; - } else { - h = (h + 1) % pairs.size(); + if (*c == nullptr || (*c)->key == key) { + return *c; } + c = &(*c)->next; } } template template -constexpr typename HashMap::Pair *&HashMap::access(Vector &pairs, KK const&k) { - auto h = static_cast(ox::hash{}(k) % pairs.size()); +constexpr UPtr::Pair> &HashMap::access( + Vector> &pairs, + KK const &key) { + auto const h = static_cast(ox::hash{}(key) % pairs.size()); + auto &p = *pairs.at(h).unwrap(); + if (p == nullptr || p->key == key) { + return p; + } + auto c = &p->next; while (true) { - auto &p = *pairs.at(h).unwrap(); - if (p == nullptr || p->key == k) { - return p; - } else { - h = (h + 1) % pairs.size(); + if (*c == nullptr || (*c)->key == key) { + return *c; } + c = &(*c)->next; } } diff --git a/deps/ox/src/ox/std/test/tests.cpp b/deps/ox/src/ox/std/test/tests.cpp index 68f5252f..d999d39d 100644 --- a/deps/ox/src/ox/std/test/tests.cpp +++ b/deps/ox/src/ox/std/test/tests.cpp @@ -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 ii; ii[4] = 42; ii[5] = 100; From f7a7a66a6a07317cf6c39bc5393a7410b45b74ff Mon Sep 17 00:00:00 2001 From: Gary Talent Date: Sat, 25 Jan 2025 22:58:18 -0600 Subject: [PATCH 07/14] [ox/event] Add Signal::connectionCnt --- deps/ox/src/ox/event/signal.hpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/deps/ox/src/ox/event/signal.hpp b/deps/ox/src/ox/event/signal.hpp index f3aacd0b..ac46e986 100644 --- a/deps/ox/src/ox/event/signal.hpp +++ b/deps/ox/src/ox/event/signal.hpp @@ -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 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; From cfa91d3d399829dab3db0f5f12d58084cd22a723 Mon Sep 17 00:00:00 2001 From: Gary Talent Date: Sat, 25 Jan 2025 22:59:01 -0600 Subject: [PATCH 08/14] [keel,studio] Add ability to rename files --- .../keel/include/keel/assetmanager.hpp | 45 +++++++++++-- src/olympic/keel/include/keel/media.hpp | 2 + src/olympic/keel/src/media.cpp | 15 +++++ src/olympic/keel/src/pack.cpp | 7 ++- src/olympic/studio/applib/src/CMakeLists.txt | 1 + .../studio/applib/src/projectexplorer.cpp | 3 + .../studio/applib/src/projectexplorer.hpp | 1 + src/olympic/studio/applib/src/renamefile.cpp | 63 +++++++++++++++++++ src/olympic/studio/applib/src/renamefile.hpp | 44 +++++++++++++ src/olympic/studio/applib/src/studioapp.cpp | 24 ++++--- src/olympic/studio/applib/src/studioapp.hpp | 9 ++- .../studio/modlib/include/studio/project.hpp | 18 ++++-- src/olympic/studio/modlib/src/project.cpp | 23 ++++--- src/olympic/turbine/src/glfw/gfx.cpp | 1 + 14 files changed, 227 insertions(+), 29 deletions(-) create mode 100644 src/olympic/studio/applib/src/renamefile.cpp create mode 100644 src/olympic/studio/applib/src/renamefile.hpp diff --git a/src/olympic/keel/include/keel/assetmanager.hpp b/src/olympic/keel/include/keel/assetmanager.hpp index 4c8ae38d..0a1525e2 100644 --- a/src/olympic/keel/include/keel/assetmanager.hpp +++ b/src/olympic/keel/include/keel/assetmanager.hpp @@ -9,7 +9,6 @@ #endif #include -#include #include #include #include @@ -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>> m_cache; public: - AssetTypeManager(Loader &&loader) noexcept: m_loader(std::move(loader)) {} + explicit AssetTypeManager(Loader &&loader) noexcept: m_loader(std::move(loader)) {} ox::Result> 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> m_assetTypeManagers; - ox::HashMap> m_fileUpdated; + ox::HashMap>> m_fileUpdated; template ox::Result*> 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> loadAsset(ox::StringViewCR assetId) noexcept { OX_REQUIRE(m, getTypeManager()); OX_REQUIRE(out, m->loadAsset(assetId)); - m_fileUpdated[assetId].connect(m, &AssetTypeManager::reloadAsset); + if (!m_fileUpdated.contains(assetId)) [[unlikely]] { + m_fileUpdated[assetId] = ox::make_unique>(); + } + m_fileUpdated[assetId]->connect(m, &AssetTypeManager::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 diff --git a/src/olympic/keel/include/keel/media.hpp b/src/olympic/keel/include/keel/media.hpp index 228bab75..7faa272e 100644 --- a/src/olympic/keel/include/keel/media.hpp +++ b/src/olympic/keel/include/keel/media.hpp @@ -47,6 +47,8 @@ ox::Result getPath(Context &ctx, ox::FileAddress const&fileAddr ox::Result getPath(Context &ctx, ox::CStringViewCR fileId) noexcept; +ox::Error updatePath(Context &ctx, ox::StringViewCR oldPath, ox::StringViewCR newPath) noexcept; + constexpr ox::Result uuidUrlToUuid(ox::StringViewCR uuidUrl) noexcept { return ox::UUID::fromString(substr(uuidUrl, 7)); } diff --git a/src/olympic/keel/src/media.cpp b/src/olympic/keel/src/media.cpp index ea59e415..01e6f90d 100644 --- a/src/olympic/keel/src/media.cpp +++ b/src/olympic/keel/src/media.cpp @@ -129,6 +129,21 @@ ox::Result 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 uuidUrlToPath(Context &ctx, ox::StringViewCR uuid) noexcept { #ifndef OX_BARE_METAL OX_REQUIRE_M(out, ctx.uuidToPath.at(substr(uuid, 7))); diff --git a/src/olympic/keel/src/pack.cpp b/src/olympic/keel/src/pack.cpp index 301ffe49..1b88bdb4 100644 --- a/src/olympic/keel/src/pack.cpp +++ b/src/olympic/keel/src/pack.cpp @@ -39,10 +39,11 @@ static ox::Error pathToInode( auto const uuid = ox::substr(path, 7); OX_RETURN_ERROR(keel::uuidToPath(ctx, uuid).to().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(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( diff --git a/src/olympic/studio/applib/src/CMakeLists.txt b/src/olympic/studio/applib/src/CMakeLists.txt index f69e669b..59f238e2 100644 --- a/src/olympic/studio/applib/src/CMakeLists.txt +++ b/src/olympic/studio/applib/src/CMakeLists.txt @@ -9,6 +9,7 @@ add_library( newmenu.cpp newproject.cpp projectexplorer.cpp + renamefile.cpp studioapp.cpp ) target_compile_definitions( diff --git a/src/olympic/studio/applib/src/projectexplorer.cpp b/src/olympic/studio/applib/src/projectexplorer.cpp index 286ab4d3..fd1fdcc6 100644 --- a/src/olympic/studio/applib/src/projectexplorer.cpp +++ b/src/olympic/studio/applib/src/projectexplorer.cpp @@ -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(); } } diff --git a/src/olympic/studio/applib/src/projectexplorer.hpp b/src/olympic/studio/applib/src/projectexplorer.hpp index 400155c7..8abe6e2d 100644 --- a/src/olympic/studio/applib/src/projectexplorer.hpp +++ b/src/olympic/studio/applib/src/projectexplorer.hpp @@ -19,6 +19,7 @@ class ProjectExplorer final: public FileExplorer { ox::Signal addItem; ox::Signal addDir; ox::Signal deleteItem; + ox::Signal renameItem; explicit ProjectExplorer(keel::Context &kctx) noexcept; diff --git a/src/olympic/studio/applib/src/renamefile.cpp b/src/olympic/studio/applib/src/renamefile.cpp new file mode 100644 index 00000000..b4c633c7 --- /dev/null +++ b/src/olympic/studio/applib/src/renamefile.cpp @@ -0,0 +1,63 @@ +/* + * Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#include + +#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; + } +} + +} \ No newline at end of file diff --git a/src/olympic/studio/applib/src/renamefile.hpp b/src/olympic/studio/applib/src/renamefile.hpp new file mode 100644 index 00000000..2af0326d --- /dev/null +++ b/src/olympic/studio/applib/src/renamefile.hpp @@ -0,0 +1,44 @@ +/* + * Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#pragma once + +#include +#include + +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 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; + +}; + +} diff --git a/src/olympic/studio/applib/src/studioapp.cpp b/src/olympic/studio/applib/src/studioapp.cpp index 05d4542d..01244728 100644 --- a/src/olympic/studio/applib/src/studioapp.cpp +++ b/src/olympic/studio/applib/src/studioapp.cpp @@ -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(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(keelCtx(m_tctx), [&](StudioConfig &config) { diff --git a/src/olympic/studio/applib/src/studioapp.hpp b/src/olympic/studio/applib/src/studioapp.hpp index 6eee9f4f..39a3e30c 100644 --- a/src/olympic/studio/applib/src/studioapp.hpp +++ b/src/olympic/studio/applib/src/studioapp.hpp @@ -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 const m_popups = { + ox::Array 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; diff --git a/src/olympic/studio/modlib/include/studio/project.hpp b/src/olympic/studio/modlib/include/studio/project.hpp index 8ee319fe..dc8a221a 100644 --- a/src/olympic/studio/modlib/include/studio/project.hpp +++ b/src/olympic/studio/modlib/include/studio/project.hpp @@ -8,7 +8,6 @@ #include #include #include -#include #include #include @@ -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> 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 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 loadBuff(ox::StringViewCR path) const noexcept; - ox::Error lsProcDir(ox::Vector *paths, ox::StringViewCR path) const noexcept; + ox::Error lsProcDir(ox::Vector &paths, ox::StringViewCR path) const noexcept; ox::Result> listFiles(ox::StringViewCR path = "") const noexcept; @@ -128,7 +130,8 @@ class Project: public ox::SignalHandler { // file. ox::Signal fileRecognized; ox::Signal fileDeleted; - ox::Signal fileUpdated; + ox::Signal fileUpdated; + ox::Signal 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 {}; } diff --git a/src/olympic/studio/modlib/src/project.cpp b/src/olympic/studio/modlib/src/project.cpp index 9548975e..4ec709a9 100644 --- a/src/olympic/studio/modlib/src/project.cpp +++ b/src/olympic/studio/modlib/src/project.cpp @@ -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 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 Project::loadBuff(ox::StringViewCR path) const noexcept { return m_fs.read(path); } -ox::Error Project::lsProcDir(ox::Vector *paths, ox::StringViewCR path) const noexcept { +ox::Error Project::lsProcDir(ox::Vector &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 *paths, ox::StringViewCR pat ox::Result> Project::listFiles(ox::StringViewCR path) const noexcept { ox::Vector paths; - OX_RETURN_ERROR(lsProcDir(&paths, path)); + OX_RETURN_ERROR(lsProcDir(paths, path)); return paths; } diff --git a/src/olympic/turbine/src/glfw/gfx.cpp b/src/olympic/turbine/src/glfw/gfx.cpp index 058cc496..40f3b103 100644 --- a/src/olympic/turbine/src/glfw/gfx.cpp +++ b/src/olympic/turbine/src/glfw/gfx.cpp @@ -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 {}; From f840240aac35346ee88e69a42a0c40bfb19c2b35 Mon Sep 17 00:00:00 2001 From: Gary Talent Date: Sat, 25 Jan 2025 22:59:51 -0600 Subject: [PATCH 09/14] [nostalgia/gfx/studio/tilesheeteditor] Rework system for tracking current palette path --- .../tilesheeteditor/tilesheeteditor-imgui.cpp | 14 ++--- .../tilesheeteditor/tilesheeteditor-imgui.hpp | 7 +-- .../tilesheeteditor/tilesheeteditormodel.cpp | 54 ++++++++++++------- .../tilesheeteditor/tilesheeteditormodel.hpp | 7 ++- 4 files changed, 47 insertions(+), 35 deletions(-) diff --git a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp index e58bb1b0..fddf29eb 100644 --- a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp +++ b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp @@ -70,8 +70,8 @@ static ox::Error toPngFile( ox::Vector &&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(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( 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(keelCtx(m_sctx), itemPath(), diff --git a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditor-imgui.hpp b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditor-imgui.hpp index 8fd58a54..ea42f57f 100644 --- a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditor-imgui.hpp +++ b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditor-imgui.hpp @@ -4,7 +4,6 @@ #pragma once -#include #include #include @@ -29,7 +28,7 @@ class TileSheetEditorImGui: public studio::Editor { public: ox::Signal 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 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; diff --git a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.cpp b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.cpp index 44fcfdd6..cf7fd36d 100644 --- a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.cpp +++ b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.cpp @@ -42,17 +42,19 @@ Palette const TileSheetEditorModel::s_defaultPalette = { }; TileSheetEditorModel::TileSheetEditorModel( - studio::StudioContext &sctx, ox::StringViewCR path, studio::UndoStack &undoStack): - m_sctx(sctx), - m_tctx(m_sctx.tctx), - m_path(path), - m_img(*readObj(keelCtx(m_tctx), m_path).unwrapThrow()), - // ignore failure to load palette - m_pal(readObj(keelCtx(m_tctx), m_img.defaultPalette).value), - m_undoStack(undoStack) { + studio::StudioContext &sctx, ox::StringParam path, studio::UndoStack &undoStack): + m_sctx(sctx), + m_tctx(m_sctx.tctx), + m_path(std::move(path)), + m_img(*readObj(keelCtx(m_tctx), m_path).unwrapThrow()), + // ignore failure to load palette + m_pal(readObj(keelCtx(m_tctx), m_img.defaultPalette).value), + m_undoStack(undoStack) { 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{cmd}); m_ongoingDrawCommand = dynamic_cast(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 {}; +} + } diff --git a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.hpp b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.hpp index ef652051..66c75a2b 100644 --- a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.hpp +++ b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.hpp @@ -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 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 { From 046834c2b92d76f21e12a3c978374382b24026a9 Mon Sep 17 00:00:00 2001 From: Gary Talent Date: Sun, 26 Jan 2025 00:52:11 -0600 Subject: [PATCH 10/14] [studio,nostalgia] Update tab name when corresponding file's name changes --- .../src/studio/paletteeditor/paletteeditor-imgui.cpp | 2 +- .../studio/tilesheeteditor/tilesheeteditor-imgui.cpp | 2 +- .../studio/tilesheeteditor/tilesheeteditormodel.cpp | 2 +- .../studio/tilesheeteditor/tilesheeteditormodel.hpp | 2 +- .../modules/scene/src/studio/sceneeditor-imgui.cpp | 2 +- src/olympic/studio/applib/src/clawviewer.cpp | 2 +- src/olympic/studio/applib/src/studioapp.cpp | 12 +++++++++++- src/olympic/studio/applib/src/studioapp.hpp | 2 +- src/olympic/studio/modlib/include/studio/editor.hpp | 5 ++++- src/olympic/studio/modlib/include/studio/project.hpp | 2 +- src/olympic/studio/modlib/src/editor.cpp | 11 ++++++++++- src/olympic/studio/modlib/src/project.cpp | 2 +- 12 files changed, 34 insertions(+), 12 deletions(-) diff --git a/src/nostalgia/modules/gfx/src/studio/paletteeditor/paletteeditor-imgui.cpp b/src/nostalgia/modules/gfx/src/studio/paletteeditor/paletteeditor-imgui.cpp index 9cdf967c..28d1275a 100644 --- a/src/nostalgia/modules/gfx/src/studio/paletteeditor/paletteeditor-imgui.cpp +++ b/src/nostalgia/modules/gfx/src/studio/paletteeditor/paletteeditor-imgui.cpp @@ -54,7 +54,7 @@ void PaletteEditorImGui::PageRenameDialog::draw(turbine::Context &tctx) noexcept PaletteEditorImGui::PaletteEditorImGui(studio::StudioContext &sctx, ox::StringParam path): - Editor(std::move(path)), + Editor(sctx, std::move(path)), m_sctx(sctx), m_tctx(sctx.tctx), m_pal(*keel::readObj(keelCtx(m_tctx), itemPath()).unwrapThrow()) { diff --git a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp index fddf29eb..288ab3b5 100644 --- a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp +++ b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp @@ -87,7 +87,7 @@ static ox::Error toPngFile( } TileSheetEditorImGui::TileSheetEditorImGui(studio::StudioContext &sctx, ox::StringParam path): - Editor(std::move(path)), + Editor(sctx, std::move(path)), m_sctx{sctx}, m_tctx{m_sctx.tctx}, m_palPicker{"Palette Chooser", keelCtx(sctx), FileExt_npal}, diff --git a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.cpp b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.cpp index cf7fd36d..b1c7fda7 100644 --- a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.cpp +++ b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.cpp @@ -329,7 +329,7 @@ void TileSheetEditorModel::pushCommand(studio::UndoCommand *cmd) noexcept { m_updated = true; } -ox::Error TileSheetEditorModel::handleFileRename(ox::StringViewCR newPath, ox::UUID const&id) noexcept { +ox::Error TileSheetEditorModel::handleFileRename(ox::StringViewCR, 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) { diff --git a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.hpp b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.hpp index 66c75a2b..6b00e8f1 100644 --- a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.hpp +++ b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.hpp @@ -137,7 +137,7 @@ class TileSheetEditorModel: public ox::SignalHandler { void pushCommand(studio::UndoCommand *cmd) noexcept; - ox::Error handleFileRename(ox::StringViewCR newPath, ox::UUID const&id) noexcept; + ox::Error handleFileRename(ox::StringViewCR, ox::StringViewCR newPath, ox::UUID const&id) noexcept; }; diff --git a/src/nostalgia/modules/scene/src/studio/sceneeditor-imgui.cpp b/src/nostalgia/modules/scene/src/studio/sceneeditor-imgui.cpp index 8a7416ca..70ccc6c3 100644 --- a/src/nostalgia/modules/scene/src/studio/sceneeditor-imgui.cpp +++ b/src/nostalgia/modules/scene/src/studio/sceneeditor-imgui.cpp @@ -11,7 +11,7 @@ namespace nostalgia::scene { SceneEditorImGui::SceneEditorImGui(studio::StudioContext &ctx, ox::StringView path): - Editor(path), + Editor(ctx, path), m_ctx(ctx.tctx), m_editor(m_ctx, path), m_view(m_ctx, m_editor.scene()) { diff --git a/src/olympic/studio/applib/src/clawviewer.cpp b/src/olympic/studio/applib/src/clawviewer.cpp index 65f895e5..d7b2e73b 100644 --- a/src/olympic/studio/applib/src/clawviewer.cpp +++ b/src/olympic/studio/applib/src/clawviewer.cpp @@ -9,7 +9,7 @@ namespace studio { ClawEditor::ClawEditor(StudioContext &sctx, ox::StringParam path): - Editor(std::move(path)), + Editor(sctx, std::move(path)), m_obj(sctx.project->loadObj(itemPath()).unwrapThrow()) { } diff --git a/src/olympic/studio/applib/src/studioapp.cpp b/src/olympic/studio/applib/src/studioapp.cpp index 01244728..9d4130a2 100644 --- a/src/olympic/studio/applib/src/studioapp.cpp +++ b/src/olympic/studio/applib/src/studioapp.cpp @@ -373,7 +373,17 @@ ox::Error StudioUI::renameFile(ox::StringViewCR path) noexcept { return m_renameFile.openPath(path); } -ox::Error StudioUI::handleMoveFile(ox::StringViewCR, 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; + break; + } + } + // needed to keep this tab open, ImGui loses track when the name changes + if (m_activeEditor) { + m_activeEditorUpdatePending = m_activeEditor; + } return m_projectExplorer.refreshProjectTreeModel(); } diff --git a/src/olympic/studio/applib/src/studioapp.hpp b/src/olympic/studio/applib/src/studioapp.hpp index 39a3e30c..06fef01e 100644 --- a/src/olympic/studio/applib/src/studioapp.hpp +++ b/src/olympic/studio/applib/src/studioapp.hpp @@ -100,7 +100,7 @@ class StudioUI: public ox::SignalHandler { ox::Error renameFile(ox::StringViewCR path) noexcept; - ox::Error handleMoveFile(ox::StringViewCR path, ox::UUID const&id) noexcept; + ox::Error handleMoveFile(ox::StringViewCR oldPath, ox::StringViewCR newPath, ox::UUID const&id) noexcept; ox::Error createOpenProject(ox::StringViewCR path) noexcept; diff --git a/src/olympic/studio/modlib/include/studio/editor.hpp b/src/olympic/studio/modlib/include/studio/editor.hpp index 14a2440a..db6db911 100644 --- a/src/olympic/studio/modlib/include/studio/editor.hpp +++ b/src/olympic/studio/modlib/include/studio/editor.hpp @@ -119,7 +119,7 @@ class Editor: public studio::BaseEditor { ox::String m_itemName; public: - Editor(ox::StringParam itemPath) noexcept; + Editor(StudioContext &ctx, ox::StringParam itemPath) noexcept; [[nodiscard]] ox::CStringView itemPath() const noexcept final; @@ -143,6 +143,9 @@ class Editor: public studio::BaseEditor { private: ox::Error markUnsavedChanges(UndoCommand const*) noexcept; + + ox::Error handleRename(ox::StringViewCR oldPath, ox::StringViewCR newPath, ox::UUID const&id) noexcept; + }; diff --git a/src/olympic/studio/modlib/include/studio/project.hpp b/src/olympic/studio/modlib/include/studio/project.hpp index dc8a221a..c058281d 100644 --- a/src/olympic/studio/modlib/include/studio/project.hpp +++ b/src/olympic/studio/modlib/include/studio/project.hpp @@ -131,7 +131,7 @@ class Project: public ox::SignalHandler { ox::Signal fileRecognized; ox::Signal fileDeleted; ox::Signal fileUpdated; - ox::Signal fileMoved; + ox::Signal fileMoved; }; diff --git a/src/olympic/studio/modlib/src/editor.cpp b/src/olympic/studio/modlib/src/editor.cpp index 04917530..8aef1515 100644 --- a/src/olympic/studio/modlib/src/editor.cpp +++ b/src/olympic/studio/modlib/src/editor.cpp @@ -107,10 +107,11 @@ UndoStack *BaseEditor::undoStack() noexcept { } -Editor::Editor(ox::StringParam itemPath) noexcept: +Editor::Editor(StudioContext &ctx, ox::StringParam itemPath) noexcept: m_itemPath(std::move(itemPath)), m_itemName(m_itemPath.substr(std::find(m_itemPath.rbegin(), m_itemPath.rend(), '/').offset() + 1)) { m_undoStack.changeTriggered.connect(this, &Editor::markUnsavedChanges); + ctx.project->fileMoved.connect(this, &Editor::handleRename); } [[nodiscard]] @@ -136,4 +137,12 @@ ox::Error Editor::markUnsavedChanges(UndoCommand const*) noexcept { return {}; } +ox::Error Editor::handleRename(ox::StringViewCR oldPath, ox::StringViewCR newPath, ox::UUID const&) noexcept { + if (m_itemPath == oldPath) { + m_itemPath = newPath; + m_itemName = m_itemPath.substr(std::find(m_itemPath.rbegin(), m_itemPath.rend(), '/').offset() + 1); + } + return {}; +} + } diff --git a/src/olympic/studio/modlib/src/project.cpp b/src/olympic/studio/modlib/src/project.cpp index 4ec709a9..8c1f7f9a 100644 --- a/src/olympic/studio/modlib/src/project.cpp +++ b/src/olympic/studio/modlib/src/project.cpp @@ -74,7 +74,7 @@ ox::Error Project::moveItem(ox::StringViewCR src, ox::StringViewCR dest) noexcep 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); + fileMoved.emit(src, dest, uuid); return {}; } From a24bf7ffb9cd94256bdbac3991d4e7ab1ac7c8b8 Mon Sep 17 00:00:00 2001 From: Gary Talent Date: Sun, 26 Jan 2025 01:01:48 -0600 Subject: [PATCH 11/14] [studio] Fix config to update when open file name changes --- src/olympic/studio/applib/src/studioapp.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/olympic/studio/applib/src/studioapp.cpp b/src/olympic/studio/applib/src/studioapp.cpp index 9d4130a2..827979d5 100644 --- a/src/olympic/studio/applib/src/studioapp.cpp +++ b/src/olympic/studio/applib/src/studioapp.cpp @@ -377,6 +377,11 @@ ox::Error StudioUI::handleMoveFile(ox::StringViewCR oldPath, ox::StringViewCR ne for (auto &f : m_openFiles) { if (f == oldPath) { f = newPath; + editConfig(keelCtx(m_sctx), [&](StudioConfig &cfg) { + auto p = find(cfg.openFiles.begin(), cfg.openFiles.end(), oldPath); + *p = newPath; + cfg.activeTabItemName = newPath; + }); break; } } From 109e1898cc9eec99b13a0a0d1ab335001731b25c Mon Sep 17 00:00:00 2001 From: Gary Talent Date: Sun, 26 Jan 2025 02:03:54 -0600 Subject: [PATCH 12/14] [studio] Add ability to drag files between directories --- .../studio/applib/src/projectexplorer.cpp | 4 ++++ .../studio/applib/src/projectexplorer.hpp | 3 +++ src/olympic/studio/applib/src/studioapp.cpp | 16 +++++++++++++++- src/olympic/studio/applib/src/studioapp.hpp | 6 ++++++ .../modlib/include/studio/filetreemodel.hpp | 2 ++ src/olympic/studio/modlib/src/filetreemodel.cpp | 9 +++++++++ 6 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/olympic/studio/applib/src/projectexplorer.cpp b/src/olympic/studio/applib/src/projectexplorer.cpp index fd1fdcc6..942548c5 100644 --- a/src/olympic/studio/applib/src/projectexplorer.cpp +++ b/src/olympic/studio/applib/src/projectexplorer.cpp @@ -26,6 +26,10 @@ void ProjectExplorer::fileDeleted(ox::StringViewCR path) const noexcept { deleteItem.emit(path); } +void ProjectExplorer::fileMoved(ox::StringViewCR src, ox::StringViewCR dst) const noexcept { + moveItem.emit(src, dst); +} + void ProjectExplorer::fileContextMenu(ox::StringViewCR path) const noexcept { if (ImGui::BeginPopupContextItem("FileMenu", ImGuiPopupFlags_MouseButtonRight)) { if (ImGui::MenuItem("Delete")) { diff --git a/src/olympic/studio/applib/src/projectexplorer.hpp b/src/olympic/studio/applib/src/projectexplorer.hpp index 8abe6e2d..e35d7e05 100644 --- a/src/olympic/studio/applib/src/projectexplorer.hpp +++ b/src/olympic/studio/applib/src/projectexplorer.hpp @@ -20,6 +20,7 @@ class ProjectExplorer final: public FileExplorer { ox::Signal addDir; ox::Signal deleteItem; ox::Signal renameItem; + ox::Signal moveItem; explicit ProjectExplorer(keel::Context &kctx) noexcept; @@ -30,6 +31,8 @@ class ProjectExplorer final: public FileExplorer { void fileDeleted(ox::StringViewCR path) const noexcept override; + void fileMoved(ox::StringViewCR src, ox::StringViewCR dst) const noexcept override; + void fileContextMenu(ox::StringViewCR path) const noexcept override; void dirContextMenu(ox::StringViewCR path) const noexcept override; diff --git a/src/olympic/studio/applib/src/studioapp.cpp b/src/olympic/studio/applib/src/studioapp.cpp index 827979d5..54ecc628 100644 --- a/src/olympic/studio/applib/src/studioapp.cpp +++ b/src/olympic/studio/applib/src/studioapp.cpp @@ -59,6 +59,8 @@ StudioUI::StudioUI(turbine::Context &ctx, ox::StringParam projectDataDir) noexce m_projectExplorer.addItem.connect(this, &StudioUI::addFile); m_projectExplorer.deleteItem.connect(this, &StudioUI::deleteFile); m_projectExplorer.renameItem.connect(this, &StudioUI::renameFile); + m_projectExplorer.moveItem.connect(this, &StudioUI::queueFileMove); + m_renameFile.moveFile.connect(this, &StudioUI::queueFileMove); m_newProject.finished.connect(this, &StudioUI::createOpenProject); m_newMenu.finished.connect(this, &StudioUI::openFile); loadModules(); @@ -135,6 +137,7 @@ void StudioUI::draw() noexcept { ImGui::End(); handleKeyInput(); m_taskRunner.update(m_tctx); + procFileMoves(); } void StudioUI::drawMenu() noexcept { @@ -409,7 +412,6 @@ 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); @@ -484,4 +486,16 @@ ox::Error StudioUI::closeFile(ox::StringViewCR path) noexcept { return {}; } +ox::Error StudioUI::queueFileMove(ox::StringParam src, ox::StringParam dst) noexcept { + m_queuedMoves.emplace_back(std::move(src), std::move(dst)); + return {}; +} + +void StudioUI::procFileMoves() noexcept { + for (auto const &m : m_queuedMoves) { + oxLogError(m_project->moveItem(m.a, m.b)); + } + m_queuedMoves.clear(); +} + } diff --git a/src/olympic/studio/applib/src/studioapp.hpp b/src/olympic/studio/applib/src/studioapp.hpp index 06fef01e..7e0a960d 100644 --- a/src/olympic/studio/applib/src/studioapp.hpp +++ b/src/olympic/studio/applib/src/studioapp.hpp @@ -40,6 +40,7 @@ class StudioUI: public ox::SignalHandler { BaseEditor *m_activeEditorOnLastDraw = nullptr; BaseEditor *m_activeEditor = nullptr; BaseEditor *m_activeEditorUpdatePending = nullptr; + ox::Vector> m_queuedMoves; NewMenu m_newMenu{keelCtx(m_tctx)}; DeleteConfirmation m_deleteConfirmation; NewDir m_newDirDialog; @@ -111,6 +112,11 @@ class StudioUI: public ox::SignalHandler { ox::Error openFileActiveTab(ox::StringViewCR path, bool makeActiveTab) noexcept; ox::Error closeFile(ox::StringViewCR path) noexcept; + + ox::Error queueFileMove(ox::StringParam src, ox::StringParam dst) noexcept; + + void procFileMoves() noexcept; + }; } diff --git a/src/olympic/studio/modlib/include/studio/filetreemodel.hpp b/src/olympic/studio/modlib/include/studio/filetreemodel.hpp index ea92ad74..673f7b94 100644 --- a/src/olympic/studio/modlib/include/studio/filetreemodel.hpp +++ b/src/olympic/studio/modlib/include/studio/filetreemodel.hpp @@ -45,6 +45,8 @@ class FileExplorer: public ox::SignalHandler { virtual void fileDeleted(ox::StringViewCR path) const noexcept; + virtual void fileMoved(ox::StringViewCR src, ox::StringViewCR dst) const noexcept; + void drawFileContextMenu(ox::CStringViewCR path) const noexcept; void drawDirContextMenu(ox::CStringViewCR path) const noexcept; diff --git a/src/olympic/studio/modlib/src/filetreemodel.cpp b/src/olympic/studio/modlib/src/filetreemodel.cpp index 3b483a1c..f0ac65e3 100644 --- a/src/olympic/studio/modlib/src/filetreemodel.cpp +++ b/src/olympic/studio/modlib/src/filetreemodel.cpp @@ -41,6 +41,8 @@ void FileExplorer::fileOpened(ox::StringViewCR) const noexcept {} void FileExplorer::fileDeleted(ox::StringViewCR) const noexcept {} +void FileExplorer::fileMoved(ox::StringViewCR, ox::StringViewCR) const noexcept {} + void FileExplorer::drawFileContextMenu(ox::CStringViewCR path) const noexcept { ig::IDStackItem const idStackItem{path}; fileContextMenu(path); @@ -91,6 +93,13 @@ void FileTreeModel::draw(turbine::Context &tctx) const noexcept { } ig::IDStackItem const idStackItem{m_name}; m_explorer.drawDirContextMenu(m_fullPath); + if (ig::DragDropTarget const dragDropTarget; dragDropTarget) { + auto const [ref, err] = ig::getDragDropPayload("FileRef"); + if (!err) { + auto const name = substr(ref.path, find(ref.path.rbegin(), ref.path.rend(), '/').offset() + 1); + m_explorer.fileMoved(ref.path, sfmt("{}/{}", m_fullPath, name)); + } + } if (nodeOpen) { for (auto const&child : m_children) { child->draw(tctx); From 1207dadee87ed78bd7e40981f62a64f5988c9202 Mon Sep 17 00:00:00 2001 From: Gary Talent Date: Sun, 26 Jan 2025 09:38:27 -0600 Subject: [PATCH 13/14] [studio] Add ability to move directories --- .../keel/include/keel/assetmanager.hpp | 11 +++++- .../studio/applib/src/projectexplorer.cpp | 4 +++ .../studio/applib/src/projectexplorer.hpp | 3 ++ src/olympic/studio/applib/src/studioapp.cpp | 10 ++++++ src/olympic/studio/applib/src/studioapp.hpp | 5 ++- .../studio/modlib/include/studio/dragdrop.hpp | 2 ++ .../modlib/include/studio/filetreemodel.hpp | 2 ++ .../studio/modlib/include/studio/project.hpp | 2 ++ .../studio/modlib/src/filetreemodel.cpp | 14 +++++++- src/olympic/studio/modlib/src/project.cpp | 35 +++++++++++++++++++ 10 files changed, 85 insertions(+), 3 deletions(-) diff --git a/src/olympic/keel/include/keel/assetmanager.hpp b/src/olympic/keel/include/keel/assetmanager.hpp index 0a1525e2..364f26f2 100644 --- a/src/olympic/keel/include/keel/assetmanager.hpp +++ b/src/olympic/keel/include/keel/assetmanager.hpp @@ -238,6 +238,9 @@ class AssetManager { } ox::Error updateAssetId(ox::StringViewCR oldId, ox::StringViewCR newId) noexcept final { + if (!m_cache.contains(oldId)) { + return {}; + } auto &o = m_cache[oldId]; auto &n = m_cache[newId]; n = std::move(o); @@ -246,11 +249,14 @@ class AssetManager { } void gc() noexcept final { - for (auto const&ack : m_cache.keys()) { + for (size_t i = 0; i < m_cache.keys().size();) { + auto const &ack = m_cache.keys()[i]; auto &ac = m_cache[ack]; if (!ac->references()) { m_cache.erase(ack); + continue; } + ++i; } } @@ -309,6 +315,9 @@ class AssetManager { if (m_fileUpdated.contains(newId)) { return ox::Error{1, "new asset ID already has an entry"}; } + if (!m_fileUpdated.contains(oldId)) { + return {}; + } auto &o = m_fileUpdated[oldId]; auto &n = m_fileUpdated[newId]; n = std::move(o); diff --git a/src/olympic/studio/applib/src/projectexplorer.cpp b/src/olympic/studio/applib/src/projectexplorer.cpp index 942548c5..f0edd82a 100644 --- a/src/olympic/studio/applib/src/projectexplorer.cpp +++ b/src/olympic/studio/applib/src/projectexplorer.cpp @@ -30,6 +30,10 @@ void ProjectExplorer::fileMoved(ox::StringViewCR src, ox::StringViewCR dst) cons moveItem.emit(src, dst); } +void ProjectExplorer::dirMoved(ox::StringViewCR src, ox::StringViewCR dst) const noexcept { + moveDir.emit(src, dst); +} + void ProjectExplorer::fileContextMenu(ox::StringViewCR path) const noexcept { if (ImGui::BeginPopupContextItem("FileMenu", ImGuiPopupFlags_MouseButtonRight)) { if (ImGui::MenuItem("Delete")) { diff --git a/src/olympic/studio/applib/src/projectexplorer.hpp b/src/olympic/studio/applib/src/projectexplorer.hpp index e35d7e05..c0b4420a 100644 --- a/src/olympic/studio/applib/src/projectexplorer.hpp +++ b/src/olympic/studio/applib/src/projectexplorer.hpp @@ -20,6 +20,7 @@ class ProjectExplorer final: public FileExplorer { ox::Signal addDir; ox::Signal deleteItem; ox::Signal renameItem; + ox::Signal moveDir; ox::Signal moveItem; explicit ProjectExplorer(keel::Context &kctx) noexcept; @@ -33,6 +34,8 @@ class ProjectExplorer final: public FileExplorer { void fileMoved(ox::StringViewCR src, ox::StringViewCR dst) const noexcept override; + void dirMoved(ox::StringViewCR src, ox::StringViewCR dst) const noexcept override; + void fileContextMenu(ox::StringViewCR path) const noexcept override; void dirContextMenu(ox::StringViewCR path) const noexcept override; diff --git a/src/olympic/studio/applib/src/studioapp.cpp b/src/olympic/studio/applib/src/studioapp.cpp index 54ecc628..64c6ba89 100644 --- a/src/olympic/studio/applib/src/studioapp.cpp +++ b/src/olympic/studio/applib/src/studioapp.cpp @@ -59,6 +59,7 @@ StudioUI::StudioUI(turbine::Context &ctx, ox::StringParam projectDataDir) noexce m_projectExplorer.addItem.connect(this, &StudioUI::addFile); m_projectExplorer.deleteItem.connect(this, &StudioUI::deleteFile); m_projectExplorer.renameItem.connect(this, &StudioUI::renameFile); + m_projectExplorer.moveDir.connect(this, &StudioUI::queueDirMove); m_projectExplorer.moveItem.connect(this, &StudioUI::queueFileMove); m_renameFile.moveFile.connect(this, &StudioUI::queueFileMove); m_newProject.finished.connect(this, &StudioUI::createOpenProject); @@ -486,6 +487,11 @@ ox::Error StudioUI::closeFile(ox::StringViewCR path) noexcept { return {}; } +ox::Error StudioUI::queueDirMove(ox::StringParam src, ox::StringParam dst) noexcept { + m_queuedDirMoves.emplace_back(std::move(src), std::move(dst)); + return {}; +} + ox::Error StudioUI::queueFileMove(ox::StringParam src, ox::StringParam dst) noexcept { m_queuedMoves.emplace_back(std::move(src), std::move(dst)); return {}; @@ -496,6 +502,10 @@ void StudioUI::procFileMoves() noexcept { oxLogError(m_project->moveItem(m.a, m.b)); } m_queuedMoves.clear(); + for (auto const &m : m_queuedDirMoves) { + oxLogError(m_project->moveDir(m.a, m.b)); + } + m_queuedDirMoves.clear(); } } diff --git a/src/olympic/studio/applib/src/studioapp.hpp b/src/olympic/studio/applib/src/studioapp.hpp index 7e0a960d..c2e6eb32 100644 --- a/src/olympic/studio/applib/src/studioapp.hpp +++ b/src/olympic/studio/applib/src/studioapp.hpp @@ -40,7 +40,8 @@ class StudioUI: public ox::SignalHandler { BaseEditor *m_activeEditorOnLastDraw = nullptr; BaseEditor *m_activeEditor = nullptr; BaseEditor *m_activeEditorUpdatePending = nullptr; - ox::Vector> m_queuedMoves; + ox::Vector> m_queuedMoves; + ox::Vector> m_queuedDirMoves; NewMenu m_newMenu{keelCtx(m_tctx)}; DeleteConfirmation m_deleteConfirmation; NewDir m_newDirDialog; @@ -113,6 +114,8 @@ class StudioUI: public ox::SignalHandler { ox::Error closeFile(ox::StringViewCR path) noexcept; + ox::Error queueDirMove(ox::StringParam src, ox::StringParam dst) noexcept; + ox::Error queueFileMove(ox::StringParam src, ox::StringParam dst) noexcept; void procFileMoves() noexcept; diff --git a/src/olympic/studio/modlib/include/studio/dragdrop.hpp b/src/olympic/studio/modlib/include/studio/dragdrop.hpp index e75bb040..b4cd2798 100644 --- a/src/olympic/studio/modlib/include/studio/dragdrop.hpp +++ b/src/olympic/studio/modlib/include/studio/dragdrop.hpp @@ -13,10 +13,12 @@ struct FileRef { static constexpr auto TypeName = "net.drinkingtea.studio.FileRef"; static constexpr auto TypeVersion = 1; ox::String path; + bool isDir{}; }; OX_MODEL_BEGIN(FileRef) OX_MODEL_FIELD(path) + OX_MODEL_FIELD(isDir) OX_MODEL_END() } diff --git a/src/olympic/studio/modlib/include/studio/filetreemodel.hpp b/src/olympic/studio/modlib/include/studio/filetreemodel.hpp index 673f7b94..3d5a3656 100644 --- a/src/olympic/studio/modlib/include/studio/filetreemodel.hpp +++ b/src/olympic/studio/modlib/include/studio/filetreemodel.hpp @@ -47,6 +47,8 @@ class FileExplorer: public ox::SignalHandler { virtual void fileMoved(ox::StringViewCR src, ox::StringViewCR dst) const noexcept; + virtual void dirMoved(ox::StringViewCR src, ox::StringViewCR dst) const noexcept; + void drawFileContextMenu(ox::CStringViewCR path) const noexcept; void drawDirContextMenu(ox::CStringViewCR path) const noexcept; diff --git a/src/olympic/studio/modlib/include/studio/project.hpp b/src/olympic/studio/modlib/include/studio/project.hpp index c058281d..76489c68 100644 --- a/src/olympic/studio/modlib/include/studio/project.hpp +++ b/src/olympic/studio/modlib/include/studio/project.hpp @@ -94,6 +94,8 @@ class Project: public ox::SignalHandler { ox::Error moveItem(ox::StringViewCR src, ox::StringViewCR dest) noexcept; + ox::Error moveDir(ox::StringViewCR src, ox::StringViewCR dest) noexcept; + ox::Error deleteItem(ox::StringViewCR path) noexcept; [[nodiscard]] diff --git a/src/olympic/studio/modlib/src/filetreemodel.cpp b/src/olympic/studio/modlib/src/filetreemodel.cpp index f0ac65e3..d7d347a0 100644 --- a/src/olympic/studio/modlib/src/filetreemodel.cpp +++ b/src/olympic/studio/modlib/src/filetreemodel.cpp @@ -43,6 +43,8 @@ void FileExplorer::fileDeleted(ox::StringViewCR) const noexcept {} void FileExplorer::fileMoved(ox::StringViewCR, ox::StringViewCR) const noexcept {} +void FileExplorer::dirMoved(ox::StringViewCR, ox::StringViewCR) const noexcept {} + void FileExplorer::drawFileContextMenu(ox::CStringViewCR path) const noexcept { ig::IDStackItem const idStackItem{path}; fileContextMenu(path); @@ -93,11 +95,21 @@ void FileTreeModel::draw(turbine::Context &tctx) const noexcept { } ig::IDStackItem const idStackItem{m_name}; m_explorer.drawDirContextMenu(m_fullPath); + if (m_explorer.fileDraggable()) { + std::ignore = ig::dragDropSource([this] { + ImGui::Text("%s", m_name.c_str()); + return ig::setDragDropPayload("FileRef", FileRef{ox::String{m_fullPath}, true}); + }); + } if (ig::DragDropTarget const dragDropTarget; dragDropTarget) { auto const [ref, err] = ig::getDragDropPayload("FileRef"); if (!err) { auto const name = substr(ref.path, find(ref.path.rbegin(), ref.path.rend(), '/').offset() + 1); - m_explorer.fileMoved(ref.path, sfmt("{}/{}", m_fullPath, name)); + if (ref.isDir) { + m_explorer.dirMoved(ref.path, sfmt("{}/{}", m_fullPath, name)); + } else { + m_explorer.fileMoved(ref.path, sfmt("{}/{}", m_fullPath, name)); + } } } if (nodeOpen) { diff --git a/src/olympic/studio/modlib/src/project.cpp b/src/olympic/studio/modlib/src/project.cpp index 8c1f7f9a..1aa311a0 100644 --- a/src/olympic/studio/modlib/src/project.cpp +++ b/src/olympic/studio/modlib/src/project.cpp @@ -26,6 +26,26 @@ static void generateTypes(ox::TypeStore &ts) noexcept { } } +static ox::Result> listAllRecursive(ox::FileSystem &fs, ox::StringViewCR path) { + // there really isn't much recourse if this function fails, just log it and move on + auto [out, outErr] = fs.ls(path); + oxLogError(outErr); + for (auto const &p : out) { + auto const [stat, statErr] = fs.stat(path); + if (statErr) { + oxLogError(statErr); + continue; + } + if (stat.fileType == ox::FileType::Directory) { + OX_REQUIRE_M(l, listAllRecursive(fs, sfmt("{}/{}", path, p))); + for (auto &c : l) { + out.emplace_back(std::move(c)); + } + } + } + return std::move(out); +} + Project::Project(keel::Context &ctx, ox::String path, ox::StringViewCR projectDataDir): m_kctx(ctx), @@ -78,6 +98,21 @@ ox::Error Project::moveItem(ox::StringViewCR src, ox::StringViewCR dest) noexcep return {}; } +ox::Error Project::moveDir(ox::StringViewCR src, ox::StringViewCR dest) noexcept { + OX_REQUIRE(files, listAllRecursive(m_fs, src)); + OX_RETURN_ERROR(m_fs.move(src, dest)); + fileMoved.emit(src, dest, ox::UUID{}); + for (auto const &op : files) { + auto const name = + substr(op, ox::find(op.rbegin(), op.rend(), '/').offset() + 1); + auto const np = sfmt("{}/{}", dest, name); + OX_RETURN_ERROR(keel::updatePath(m_kctx, op, np)); + OX_REQUIRE(uuid, keel::pathToUuid(m_kctx, dest)); + fileMoved.emit(src, dest, uuid); + } + return {}; +} + ox::Error Project::deleteItem(ox::StringViewCR path) noexcept { OX_REQUIRE(stat, m_fs.stat(path)); if (stat.fileType == ox::FileType::Directory) { From be51838775cd37d8c0778378a5d944f8f261830c Mon Sep 17 00:00:00 2001 From: Gary Talent Date: Sun, 26 Jan 2025 15:41:13 -0600 Subject: [PATCH 14/14] [nostalgia/gfx/studio/tilesheet] Add flip x and flip y functionality --- .../gfx/include/nostalgia/gfx/tilesheet.hpp | 4 ++ .../tilesheeteditor/commands/CMakeLists.txt | 1 + .../tilesheeteditor/commands/commands.hpp | 18 ++--- .../tilesheeteditor/commands/flipcommand.cpp | 72 +++++++++++++++++++ .../tilesheeteditor/commands/flipcommand.hpp | 63 ++++++++++++++++ .../tilesheeteditor/tilesheeteditor-imgui.cpp | 14 +++- .../tilesheeteditor/tilesheeteditormodel.cpp | 47 +++++++++--- .../tilesheeteditor/tilesheeteditormodel.hpp | 6 +- src/nostalgia/modules/gfx/src/tilesheet.cpp | 24 +++++++ 9 files changed, 228 insertions(+), 21 deletions(-) create mode 100644 src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/flipcommand.cpp create mode 100644 src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/flipcommand.hpp diff --git a/src/nostalgia/modules/gfx/include/nostalgia/gfx/tilesheet.hpp b/src/nostalgia/modules/gfx/include/nostalgia/gfx/tilesheet.hpp index 4b5b9499..3a67adc2 100644 --- a/src/nostalgia/modules/gfx/include/nostalgia/gfx/tilesheet.hpp +++ b/src/nostalgia/modules/gfx/include/nostalgia/gfx/tilesheet.hpp @@ -414,6 +414,10 @@ void setPixel(TileSheet::SubSheet &ss, ox::Point const&pt, uint8_t palIdx) noexc ox::Error setPixelCount(TileSheet::SubSheet &ss, std::size_t cnt) noexcept; +void flipX(TileSheet::SubSheet &ss, ox::Point const &a, ox::Point const &b) noexcept; + +void flipY(TileSheet::SubSheet &ss, ox::Point const &a, ox::Point const &b) noexcept; + /** * Gets a count of the pixels in this sheet, and not that of its children. * @return a count of the pixels in this sheet diff --git a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/CMakeLists.txt b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/CMakeLists.txt index 7096243e..df125ef6 100644 --- a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/CMakeLists.txt +++ b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/CMakeLists.txt @@ -4,6 +4,7 @@ target_sources( cutpastecommand.cpp deletetilescommand.cpp drawcommand.cpp + flipcommand.cpp inserttilescommand.cpp palettechangecommand.cpp rmsubsheetcommand.cpp diff --git a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/commands.hpp b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/commands.hpp index 68b15e3c..5d8302eb 100644 --- a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/commands.hpp +++ b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/commands.hpp @@ -12,14 +12,16 @@ namespace nostalgia::gfx { // Command IDs to use with QUndoCommand::id() enum class CommandId { Draw = 1, - AddSubSheet = 2, - RmSubSheet = 3, - DeleteTile = 4, - InsertTile = 4, - UpdateSubSheet = 5, - Cut = 6, - Paste = 7, - PaletteChange = 8, + AddSubSheet, + RmSubSheet, + DeleteTile, + FlipXCommand, + FlipYCommand, + InsertTile, + UpdateSubSheet, + Cut, + Paste, + PaletteChange, }; constexpr bool operator==(CommandId c, int i) noexcept { diff --git a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/flipcommand.cpp b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/flipcommand.cpp new file mode 100644 index 00000000..6acc1274 --- /dev/null +++ b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/flipcommand.cpp @@ -0,0 +1,72 @@ +/* + * Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#include "flipcommand.hpp" + +#include + +namespace nostalgia::gfx { + +FlipXCommand::FlipXCommand( + TileSheet &img, + TileSheet::SubSheetIdx subSheetIdx, + ox::Point const &a, + ox::Point const &b) noexcept: + m_img{img}, + m_subSheetIdx{std::move(subSheetIdx)}, + m_ptA{a}, + m_ptB{b} {} + +ox::Error FlipXCommand::redo() noexcept { + auto &ss = getSubSheet(m_img, m_subSheetIdx); + flipX(ss, m_ptA, m_ptB); + return {}; +} + +ox::Error FlipXCommand::undo() noexcept { + auto &ss = getSubSheet(m_img, m_subSheetIdx); + flipX(ss, m_ptA, m_ptB); + return {}; +} + +int FlipXCommand::commandId() const noexcept { + return static_cast(CommandId::FlipXCommand); +} + +TileSheet::SubSheetIdx const &FlipXCommand::subsheetIdx() const noexcept { + return m_subSheetIdx; +} + + +FlipYCommand::FlipYCommand( + TileSheet &img, + TileSheet::SubSheetIdx subSheetIdx, + ox::Point const &a, + ox::Point const &b) noexcept: + m_img{img}, + m_subSheetIdx{std::move(subSheetIdx)}, + m_ptA{a}, + m_ptB{b} {} + +ox::Error FlipYCommand::redo() noexcept { + auto &ss = getSubSheet(m_img, m_subSheetIdx); + flipY(ss, m_ptA, m_ptB); + return {}; +} + +ox::Error FlipYCommand::undo() noexcept { + auto &ss = getSubSheet(m_img, m_subSheetIdx); + flipY(ss, m_ptA, m_ptB); + return {}; +} + +int FlipYCommand::commandId() const noexcept { + return static_cast(CommandId::FlipYCommand); +} + +TileSheet::SubSheetIdx const &FlipYCommand::subsheetIdx() const noexcept { + return m_subSheetIdx; +} + +} diff --git a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/flipcommand.hpp b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/flipcommand.hpp new file mode 100644 index 00000000..8d1d7b34 --- /dev/null +++ b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/commands/flipcommand.hpp @@ -0,0 +1,63 @@ +/* + * Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#pragma once + +#include "commands.hpp" + +namespace nostalgia::gfx { + +class FlipXCommand: public TileSheetCommand { + private: + TileSheet &m_img; + TileSheet::SubSheetIdx m_subSheetIdx; + ox::Point const m_ptA; + ox::Point const m_ptB; + + public: + FlipXCommand( + TileSheet &img, + TileSheet::SubSheetIdx subSheetIdx, + ox::Point const &a, + ox::Point const &b) noexcept; + + ox::Error redo() noexcept final; + + ox::Error undo() noexcept final; + + [[nodiscard]] + int commandId() const noexcept final; + + [[nodiscard]] + TileSheet::SubSheetIdx const&subsheetIdx() const noexcept override; + +}; + +class FlipYCommand: public TileSheetCommand { + private: + TileSheet &m_img; + TileSheet::SubSheetIdx m_subSheetIdx; + ox::Point const m_ptA; + ox::Point const m_ptB; + + public: + FlipYCommand( + TileSheet &img, + TileSheet::SubSheetIdx subSheetIdx, + ox::Point const &a, + ox::Point const &b) noexcept; + + ox::Error redo() noexcept final; + + ox::Error undo() noexcept final; + + [[nodiscard]] + int commandId() const noexcept final; + + [[nodiscard]] + TileSheet::SubSheetIdx const&subsheetIdx() const noexcept override; + +}; + +} diff --git a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp index 288ab3b5..c45a6692 100644 --- a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp +++ b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp @@ -222,7 +222,19 @@ void TileSheetEditorImGui::draw(studio::StudioContext&) noexcept { } } ImGui::EndChild(); - auto const ySize = controlsSize.y - 38; + ImGui::BeginChild("OperationsBox", {s_palViewWidth - 24, ig::BtnSz.y + 17}, true); + { + auto constexpr btnSz = ImVec2{55, ig::BtnSz.y}; + if (ig::PushButton("Flip X", btnSz)) { + oxLogError(m_model.flipX()); + } + ImGui::SameLine(); + if (ig::PushButton("Flip Y", btnSz)) { + oxLogError(m_model.flipY()); + } + } + ImGui::EndChild(); + auto const ySize = controlsSize.y - (38 + ig::BtnSz.y + 21); // draw palette/color picker ImGui::BeginChild("Palette", {s_palViewWidth - 24, ySize / 2.f}, true); { diff --git a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.cpp b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.cpp index b1c7fda7..99ab1b70 100644 --- a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.cpp +++ b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.cpp @@ -22,6 +22,8 @@ #include "commands/updatesubsheetcommand.hpp" #include "tilesheeteditormodel.hpp" +#include "commands/flipcommand.hpp" + namespace nostalgia::gfx { // delete pixels of all non-leaf nodes @@ -75,7 +77,7 @@ void TileSheetEditorModel::cut() { auto const pt1 = m_selection->a; auto const pt2 = ox::Point{s.columns * TileWidth, s.rows * TileHeight}; turbine::setClipboardObject(m_tctx, std::move(cb)); - pushCommand(ox::make( + std::ignore = pushCommand(ox::make( CommandId::Cut, m_img, m_activeSubsSheetIdx, pt1, pt2, blankCb)); } @@ -108,7 +110,7 @@ void TileSheetEditorModel::paste() { auto const&s = activeSubSheet(); auto const pt1 = m_selection->a; auto const pt2 = ox::Point{s.columns * TileWidth, s.rows * TileHeight}; - pushCommand(ox::make( + std::ignore = pushCommand(ox::make( CommandId::Paste, m_img, m_activeSubsSheetIdx, pt1, pt2, *cb)); } @@ -123,7 +125,7 @@ ox::StringView TileSheetEditorModel::palPath() const noexcept { ox::Error TileSheetEditorModel::setPalette(ox::StringViewCR path) noexcept { OX_REQUIRE(uuid, keelCtx(m_tctx).pathToUuid.at(path)); - pushCommand(ox::make( + std::ignore = pushCommand(ox::make( activeSubSheetIdx(), m_img, uuid->toString())); return {}; } @@ -146,7 +148,7 @@ void TileSheetEditorModel::drawCommand(ox::Point const&pt, std::size_t const pal if (m_ongoingDrawCommand) { m_updated = m_updated || m_ongoingDrawCommand->append(idx); } else if (getPixel(activeSubSheet, idx) != palIdx) { - pushCommand(ox::make( + std::ignore = pushCommand(ox::make( m_img, m_activeSubsSheetIdx, idx, static_cast(palIdx))); } } @@ -156,27 +158,27 @@ void TileSheetEditorModel::endDrawCommand() noexcept { } void TileSheetEditorModel::addSubsheet(TileSheet::SubSheetIdx const&parentIdx) noexcept { - pushCommand(ox::make(m_img, parentIdx)); + std::ignore = pushCommand(ox::make(m_img, parentIdx)); } void TileSheetEditorModel::rmSubsheet(TileSheet::SubSheetIdx const&idx) noexcept { - pushCommand(ox::make(m_img, idx)); + std::ignore = pushCommand(ox::make(m_img, idx)); } void TileSheetEditorModel::insertTiles( TileSheet::SubSheetIdx const&idx, std::size_t const tileIdx, std::size_t const tileCnt) noexcept { - pushCommand(ox::make(m_img, idx, tileIdx, tileCnt)); + std::ignore = pushCommand(ox::make(m_img, idx, tileIdx, tileCnt)); } void TileSheetEditorModel::deleteTiles( TileSheet::SubSheetIdx const&idx, std::size_t const tileIdx, std::size_t const tileCnt) noexcept { - pushCommand(ox::make(m_img, idx, tileIdx, tileCnt)); + std::ignore = pushCommand(ox::make(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(m_img, idx, name, cols, rows)); - pushCommand(cmd); + std::ignore = pushCommand(cmd); return {}; } @@ -206,7 +208,7 @@ 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) { - pushCommand(ox::make(m_img, m_activeSubsSheetIdx, idxList, palIdx)); + std::ignore = pushCommand(ox::make(m_img, m_activeSubsSheetIdx, idxList, palIdx)); } } @@ -279,6 +281,28 @@ bool TileSheetEditorModel::pixelSelected(std::size_t const idx) const noexcept { return m_selection && m_selection->contains(pt); } +ox::Error TileSheetEditorModel::flipX() noexcept { + auto const &ss = activeSubSheet(); + ox::Point a; + ox::Point b{ss.columns * TileWidth - 1, ss.rows * TileHeight - 1}; + if (m_selection) { + a = m_selection->a; + b = m_selection->b; + } + return pushCommand(ox::make(m_img, m_activeSubsSheetIdx, a, b)); +} + +ox::Error TileSheetEditorModel::flipY() noexcept { + auto const &ss = activeSubSheet(); + ox::Point a; + ox::Point b{ss.columns * TileWidth - 1, ss.rows * TileHeight - 1}; + if (m_selection) { + a = m_selection->a; + b = m_selection->b; + } + return pushCommand(ox::make(m_img, m_activeSubsSheetIdx, a, b)); +} + void TileSheetEditorModel::getFillPixels( TileSheet::SubSheet const&activeSubSheet, ox::Span pixels, @@ -323,10 +347,11 @@ void TileSheetEditorModel::setPalPath() noexcept { } } -void TileSheetEditorModel::pushCommand(studio::UndoCommand *cmd) noexcept { +ox::Error TileSheetEditorModel::pushCommand(studio::UndoCommand *cmd) noexcept { std::ignore = m_undoStack.push(ox::UPtr{cmd}); m_ongoingDrawCommand = dynamic_cast(cmd); m_updated = true; + return {}; } ox::Error TileSheetEditorModel::handleFileRename(ox::StringViewCR, ox::StringViewCR newPath, ox::UUID const&id) noexcept { diff --git a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.hpp b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.hpp index 6b00e8f1..9af2ec86 100644 --- a/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.hpp +++ b/src/nostalgia/modules/gfx/src/studio/tilesheeteditor/tilesheeteditormodel.hpp @@ -126,6 +126,10 @@ class TileSheetEditorModel: public ox::SignalHandler { bool pixelSelected(std::size_t idx) const noexcept; + ox::Error flipX() noexcept; + + ox::Error flipY() noexcept; + private: void getFillPixels( TileSheet::SubSheet const&activeSubSheet, @@ -135,7 +139,7 @@ class TileSheetEditorModel: public ox::SignalHandler { void setPalPath() noexcept; - void pushCommand(studio::UndoCommand *cmd) noexcept; + ox::Error pushCommand(studio::UndoCommand *cmd) noexcept; ox::Error handleFileRename(ox::StringViewCR, ox::StringViewCR newPath, ox::UUID const&id) noexcept; diff --git a/src/nostalgia/modules/gfx/src/tilesheet.cpp b/src/nostalgia/modules/gfx/src/tilesheet.cpp index a3aa903c..078da34a 100644 --- a/src/nostalgia/modules/gfx/src/tilesheet.cpp +++ b/src/nostalgia/modules/gfx/src/tilesheet.cpp @@ -103,6 +103,30 @@ ox::Error setPixelCount(TileSheet::SubSheet &ss, std::size_t const cnt) noexcept return setPixelCount(ss.pixels, cnt); } +void flipX(TileSheet::SubSheet &ss, ox::Point const &a, ox::Point const &b) noexcept { + auto const w = (b.x - a.x) / 2 + 1; + auto const pixCols = ss.columns * TileWidth; + for (int32_t yi = a.y; yi <= b.y; ++yi) { + for (int32_t xi = 0; xi < w; ++xi) { + auto const aIdx = ptToIdx(a.x + xi, yi, pixCols); + auto const bIdx = ptToIdx(b.x - xi, yi, pixCols); + std::swap(ss.pixels[aIdx], ss.pixels[bIdx]); + } + } +} + +void flipY(TileSheet::SubSheet &ss, ox::Point const &a, ox::Point const &b) noexcept { + auto const h = (b.y - a.y) / 2 + 1; + auto const pixCols = ss.columns * TileWidth; + for (int32_t yi = 0; yi < h; ++yi) { + for (int32_t xi = a.x; xi <= b.x; ++xi) { + auto const aIdx = ptToIdx(xi, a.y + yi, pixCols); + auto const bIdx = ptToIdx(xi, b.y - yi, pixCols); + std::swap(ss.pixels[aIdx], ss.pixels[bIdx]); + } + } +} + unsigned pixelCnt(TileSheet::SubSheet const&ss) noexcept { return static_cast(ss.pixels.size()); }