Merge commit 'ab760b064fd6a302bad13274e0e02b2b2c957b67'
This commit is contained in:
commit
8d04af691e
10
deps/ox/src/ox/event/signal.hpp
vendored
10
deps/ox/src/ox/event/signal.hpp
vendored
@ -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;
|
||||
|
132
deps/ox/src/ox/std/hashmap.hpp
vendored
132
deps/ox/src/ox/std/hashmap.hpp
vendored
@ -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>
|
||||
@ -101,7 +102,7 @@ constexpr HashMap<K, T>::~HashMap() {
|
||||
}
|
||||
|
||||
template<typename K, typename T>
|
||||
constexpr bool HashMap<K, T>::operator==(HashMap const&other) const {
|
||||
constexpr bool HashMap<K, T>::operator==(HashMap const &other) const {
|
||||
if (m_keys != other.m_keys) {
|
||||
return false;
|
||||
}
|
||||
@ -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,27 +203,26 @@ 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());
|
||||
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<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];
|
||||
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<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());
|
||||
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 == 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<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());
|
||||
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 == 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
4
deps/ox/src/ox/std/memory.hpp
vendored
4
deps/ox/src/ox/std/memory.hpp
vendored
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
10
deps/ox/src/ox/std/test/tests.cpp
vendored
10
deps/ox/src/ox/std/test/tests.cpp
vendored
@ -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;
|
||||
|
@ -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-<num key> 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.
|
||||
|
@ -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
|
||||
|
@ -9,9 +9,10 @@
|
||||
namespace nostalgia::gfx {
|
||||
|
||||
MoveColorCommand::MoveColorCommand(
|
||||
Palette &pal, size_t page, size_t srcIdx, size_t dstIdx) noexcept:
|
||||
Palette &pal,
|
||||
size_t const srcIdx,
|
||||
size_t const dstIdx) noexcept:
|
||||
m_pal(pal),
|
||||
m_page(page),
|
||||
m_srcIdx(srcIdx),
|
||||
m_dstIdx(dstIdx) {}
|
||||
|
||||
@ -29,10 +30,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 {
|
||||
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]);
|
||||
std::ignore = m_pal.colorNames.erase(srcIdx);
|
||||
m_pal.colorNames.emplace(dstIdx, std::move(name));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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) {
|
||||
@ -45,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<Palette>(keelCtx(m_tctx), itemPath()).unwrapThrow()) {
|
||||
@ -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<MoveColorCommand>(
|
||||
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<MoveColorCommand>(
|
||||
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<ColorDragDrop>();
|
||||
if (!err) {
|
||||
std::ignore = pushCommand<MoveColorCommand>(m_pal, src.i, i);
|
||||
}
|
||||
}
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ target_sources(
|
||||
cutpastecommand.cpp
|
||||
deletetilescommand.cpp
|
||||
drawcommand.cpp
|
||||
flipcommand.cpp
|
||||
inserttilescommand.cpp
|
||||
palettechangecommand.cpp
|
||||
rmsubsheetcommand.cpp
|
||||
|
@ -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 {
|
||||
|
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include "flipcommand.hpp"
|
||||
|
||||
#include <utility>
|
||||
|
||||
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<int>(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<int>(CommandId::FlipYCommand);
|
||||
}
|
||||
|
||||
TileSheet::SubSheetIdx const &FlipYCommand::subsheetIdx() const noexcept {
|
||||
return m_subSheetIdx;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
};
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
@ -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},
|
||||
@ -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;
|
||||
}
|
||||
@ -223,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);
|
||||
{
|
||||
@ -526,14 +537,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(),
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
@ -42,17 +44,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<TileSheet>(keelCtx(m_tctx), m_path).unwrapThrow()),
|
||||
// ignore failure to load palette
|
||||
m_pal(readObj<Palette>(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<TileSheet>(keelCtx(m_tctx), m_path).unwrapThrow()),
|
||||
// ignore failure to load palette
|
||||
m_pal(readObj<Palette>(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() {
|
||||
@ -73,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<CutPasteCommand>(
|
||||
std::ignore = pushCommand(ox::make<CutPasteCommand>(
|
||||
CommandId::Cut, m_img, m_activeSubsSheetIdx, pt1, pt2, blankCb));
|
||||
}
|
||||
|
||||
@ -106,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<CutPasteCommand>(
|
||||
std::ignore = pushCommand(ox::make<CutPasteCommand>(
|
||||
CommandId::Paste, m_img, m_activeSubsSheetIdx, pt1, pt2, *cb));
|
||||
}
|
||||
|
||||
@ -116,23 +120,12 @@ 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 {
|
||||
OX_REQUIRE(uuid, keelCtx(m_tctx).pathToUuid.at(path));
|
||||
pushCommand(ox::make<PaletteChangeCommand>(
|
||||
std::ignore = pushCommand(ox::make<PaletteChangeCommand>(
|
||||
activeSubSheetIdx(), m_img, uuid->toString()));
|
||||
return {};
|
||||
}
|
||||
@ -155,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<DrawCommand>(
|
||||
std::ignore = pushCommand(ox::make<DrawCommand>(
|
||||
m_img, m_activeSubsSheetIdx, idx, static_cast<int>(palIdx)));
|
||||
}
|
||||
}
|
||||
@ -165,27 +158,27 @@ void TileSheetEditorModel::endDrawCommand() noexcept {
|
||||
}
|
||||
|
||||
void TileSheetEditorModel::addSubsheet(TileSheet::SubSheetIdx const&parentIdx) noexcept {
|
||||
pushCommand(ox::make<AddSubSheetCommand>(m_img, parentIdx));
|
||||
std::ignore = pushCommand(ox::make<AddSubSheetCommand>(m_img, parentIdx));
|
||||
}
|
||||
|
||||
void TileSheetEditorModel::rmSubsheet(TileSheet::SubSheetIdx const&idx) noexcept {
|
||||
pushCommand(ox::make<RmSubSheetCommand>(m_img, idx));
|
||||
std::ignore = pushCommand(ox::make<RmSubSheetCommand>(m_img, idx));
|
||||
}
|
||||
|
||||
void TileSheetEditorModel::insertTiles(
|
||||
TileSheet::SubSheetIdx const&idx, std::size_t const tileIdx, std::size_t const tileCnt) noexcept {
|
||||
pushCommand(ox::make<InsertTilesCommand>(m_img, idx, tileIdx, tileCnt));
|
||||
std::ignore = pushCommand(ox::make<InsertTilesCommand>(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<DeleteTilesCommand>(m_img, idx, tileIdx, tileCnt));
|
||||
std::ignore = pushCommand(ox::make<DeleteTilesCommand>(m_img, idx, tileIdx, tileCnt));
|
||||
}
|
||||
|
||||
ox::Error TileSheetEditorModel::updateSubsheet(
|
||||
TileSheet::SubSheetIdx const&idx, ox::StringViewCR name, int const cols, int const rows) noexcept {
|
||||
OX_REQUIRE(cmd, ox::makeCatch<UpdateSubSheetCommand>(m_img, idx, name, cols, rows));
|
||||
pushCommand(cmd);
|
||||
std::ignore = pushCommand(cmd);
|
||||
return {};
|
||||
}
|
||||
|
||||
@ -215,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<DrawCommand>(m_img, m_activeSubsSheetIdx, idxList, palIdx));
|
||||
std::ignore = pushCommand(ox::make<DrawCommand>(m_img, m_activeSubsSheetIdx, idxList, palIdx));
|
||||
}
|
||||
}
|
||||
|
||||
@ -288,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<FlipXCommand>(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<FlipYCommand>(m_img, m_activeSubsSheetIdx, a, b));
|
||||
}
|
||||
|
||||
void TileSheetEditorModel::getFillPixels(
|
||||
TileSheet::SubSheet const&activeSubSheet,
|
||||
ox::Span<bool> pixels,
|
||||
@ -318,10 +333,34 @@ void TileSheetEditorModel::getFillPixels(
|
||||
}
|
||||
}
|
||||
|
||||
void TileSheetEditorModel::pushCommand(studio::UndoCommand *cmd) noexcept {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
ox::Error 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;
|
||||
return {};
|
||||
}
|
||||
|
||||
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) {
|
||||
m_palPath = newPath;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
@ -125,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,
|
||||
@ -132,7 +137,11 @@ class TileSheetEditorModel: public ox::SignalHandler {
|
||||
ox::Point const&pt,
|
||||
int oldColor) const noexcept;
|
||||
|
||||
void pushCommand(studio::UndoCommand *cmd) noexcept;
|
||||
void setPalPath() noexcept;
|
||||
|
||||
ox::Error pushCommand(studio::UndoCommand *cmd) noexcept;
|
||||
|
||||
ox::Error handleFileRename(ox::StringViewCR, ox::StringViewCR newPath, ox::UUID const&id) noexcept;
|
||||
|
||||
};
|
||||
|
||||
|
@ -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<unsigned>(ss.pixels.size());
|
||||
}
|
||||
|
@ -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()) {
|
||||
|
@ -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,18 +237,33 @@ class AssetManager {
|
||||
return {};
|
||||
}
|
||||
|
||||
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);
|
||||
m_cache.erase(oldId);
|
||||
return {};
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
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 +295,7 @@ class AssetManager {
|
||||
}
|
||||
|
||||
ox::Error reloadAsset(ox::StringViewCR assetId) noexcept {
|
||||
m_fileUpdated[assetId].emit(assetId);
|
||||
m_fileUpdated[assetId]->emit(assetId);
|
||||
return {};
|
||||
}
|
||||
|
||||
@ -287,15 +303,43 @@ 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"};
|
||||
}
|
||||
if (!m_fileUpdated.contains(oldId)) {
|
||||
return {};
|
||||
}
|
||||
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
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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)));
|
||||
|
@ -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(
|
||||
|
@ -9,6 +9,7 @@ add_library(
|
||||
newmenu.cpp
|
||||
newproject.cpp
|
||||
projectexplorer.cpp
|
||||
renamefile.cpp
|
||||
studioapp.cpp
|
||||
)
|
||||
target_compile_definitions(
|
||||
|
@ -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<ox::ModelObject>(itemPath()).unwrapThrow()) {
|
||||
}
|
||||
|
||||
|
@ -26,11 +26,22 @@ 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::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")) {
|
||||
deleteItem.emit(path);
|
||||
}
|
||||
if (ImGui::MenuItem("Rename")) {
|
||||
renameItem.emit(path);
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <ox/event/signal.hpp>
|
||||
#include <ox/std/memory.hpp>
|
||||
|
||||
#include <studio/filetreemodel.hpp>
|
||||
#include <studio/widget.hpp>
|
||||
@ -20,6 +19,9 @@ 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;
|
||||
ox::Signal<ox::Error(ox::StringViewCR src, ox::StringViewCR dst)> moveDir;
|
||||
ox::Signal<ox::Error(ox::StringViewCR src, ox::StringViewCR dst)> moveItem;
|
||||
|
||||
explicit ProjectExplorer(keel::Context &kctx) noexcept;
|
||||
|
||||
@ -30,6 +32,10 @@ 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 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;
|
||||
|
63
src/olympic/studio/applib/src/renamefile.cpp
Normal file
63
src/olympic/studio/applib/src/renamefile.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
44
src/olympic/studio/applib/src/renamefile.hpp
Normal file
44
src/olympic/studio/applib/src/renamefile.hpp
Normal 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;
|
||||
|
||||
};
|
||||
|
||||
}
|
@ -47,20 +47,23 @@ 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_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);
|
||||
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));
|
||||
@ -135,6 +138,7 @@ void StudioUI::draw() noexcept {
|
||||
ImGui::End();
|
||||
handleKeyInput();
|
||||
m_taskRunner.update(m_tctx);
|
||||
procFileMoves();
|
||||
}
|
||||
|
||||
void StudioUI::drawMenu() noexcept {
|
||||
@ -369,6 +373,29 @@ 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 oldPath, ox::StringViewCR newPath, ox::UUID const&) noexcept {
|
||||
for (auto &f : m_openFiles) {
|
||||
if (f == oldPath) {
|
||||
f = newPath;
|
||||
editConfig<StudioConfig>(keelCtx(m_sctx), [&](StudioConfig &cfg) {
|
||||
auto p = find(cfg.openFiles.begin(), cfg.openFiles.end(), oldPath);
|
||||
*p = newPath;
|
||||
cfg.activeTabItemName = newPath;
|
||||
});
|
||||
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();
|
||||
}
|
||||
|
||||
ox::Error StudioUI::createOpenProject(ox::StringViewCR path) noexcept {
|
||||
std::error_code ec;
|
||||
std::filesystem::create_directories(toStdStringView(path), ec);
|
||||
@ -390,6 +417,7 @@ ox::Error StudioUI::openProjectPath(ox::StringParam path) noexcept {
|
||||
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) {
|
||||
@ -459,4 +487,25 @@ 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 {};
|
||||
}
|
||||
|
||||
void StudioUI::procFileMoves() noexcept {
|
||||
for (auto const &m : m_queuedMoves) {
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include "newmenu.hpp"
|
||||
#include "newproject.hpp"
|
||||
#include "projectexplorer.hpp"
|
||||
#include "renamefile.hpp"
|
||||
|
||||
namespace studio {
|
||||
|
||||
@ -39,17 +40,21 @@ class StudioUI: public ox::SignalHandler {
|
||||
BaseEditor *m_activeEditorOnLastDraw = nullptr;
|
||||
BaseEditor *m_activeEditor = nullptr;
|
||||
BaseEditor *m_activeEditorUpdatePending = nullptr;
|
||||
ox::Vector<ox::Pair<ox::String>> m_queuedMoves;
|
||||
ox::Vector<ox::Pair<ox::String>> m_queuedDirMoves;
|
||||
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 +100,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 oldPath, ox::StringViewCR newPath, ox::UUID const&id) noexcept;
|
||||
|
||||
ox::Error createOpenProject(ox::StringViewCR path) noexcept;
|
||||
|
||||
ox::Error openProjectPath(ox::StringParam path) noexcept;
|
||||
@ -104,6 +113,13 @@ class StudioUI: public ox::SignalHandler {
|
||||
ox::Error openFileActiveTab(ox::StringViewCR path, bool makeActiveTab) noexcept;
|
||||
|
||||
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;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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()
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
@ -45,6 +45,10 @@ class FileExplorer: public ox::SignalHandler {
|
||||
|
||||
virtual void fileDeleted(ox::StringViewCR path) const noexcept;
|
||||
|
||||
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;
|
||||
|
@ -34,19 +34,37 @@ ox::Result<T> getDragDropPayload(ox::CStringViewCR name) noexcept {
|
||||
static_cast<size_t>(payload->DataSize)});
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
ox::Result<T> getDragDropPayload() noexcept {
|
||||
auto const payload = ImGui::AcceptDragDropPayload(ox::ModelTypeName_v<T>);
|
||||
if (!payload) {
|
||||
return ox::Error(1, "No drag/drop payload");
|
||||
}
|
||||
return ox::readClaw<T>({
|
||||
reinterpret_cast<char const*>(payload->Data),
|
||||
static_cast<size_t>(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<typename T>
|
||||
ox::Error setDragDropPayload(T const&obj) noexcept {
|
||||
OX_REQUIRE(buff, ox::writeClaw(obj, ox::ClawFormat::Metal));
|
||||
ImGui::SetDragDropPayload(ox::ModelTypeName_v<T>, buff.data(), buff.size());
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
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<decltype(cb()), ox::Error>) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -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,10 @@ 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 moveDir(ox::StringViewCR src, ox::StringViewCR dest) noexcept;
|
||||
|
||||
ox::Error deleteItem(ox::StringViewCR path) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
@ -114,7 +118,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 +132,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 oldPath, ox::StringViewCR newPath, ox::UUID const&id)> fileMoved;
|
||||
|
||||
};
|
||||
|
||||
@ -151,8 +156,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 +205,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 {};
|
||||
}
|
||||
|
@ -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 {};
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -41,6 +41,10 @@ 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::dirMoved(ox::StringViewCR, ox::StringViewCR) const noexcept {}
|
||||
|
||||
void FileExplorer::drawFileContextMenu(ox::CStringViewCR path) const noexcept {
|
||||
ig::IDStackItem const idStackItem{path};
|
||||
fileContextMenu(path);
|
||||
@ -91,6 +95,23 @@ 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>("FileRef");
|
||||
if (!err) {
|
||||
auto const name = substr(ref.path, find(ref.path.rbegin(), ref.path.rend(), '/').offset() + 1);
|
||||
if (ref.isDir) {
|
||||
m_explorer.dirMoved(ref.path, sfmt("{}/{}", m_fullPath, name));
|
||||
} else {
|
||||
m_explorer.fileMoved(ref.path, sfmt("{}/{}", m_fullPath, name));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nodeOpen) {
|
||||
for (auto const&child : m_children) {
|
||||
child->draw(tctx);
|
||||
|
@ -26,16 +26,37 @@ static void generateTypes(ox::TypeStore &ts) noexcept {
|
||||
}
|
||||
}
|
||||
|
||||
static ox::Result<ox::Vector<ox::String>> 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_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 (ox::defines::Debug) {
|
||||
if constexpr(ox::defines::Debug) {
|
||||
OX_THROW_ERROR(writeTypeStore());
|
||||
}
|
||||
buildFileIndex();
|
||||
@ -69,6 +90,29 @@ 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(src, dest, uuid);
|
||||
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) {
|
||||
@ -142,7 +186,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 +206,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 +227,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;
|
||||
}
|
||||
|
||||
|
@ -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 {};
|
||||
|
Loading…
x
Reference in New Issue
Block a user