[nostalgia/studio] Fix TileSheetEditor to switch to appropriate subsheet on undo/redo

This commit is contained in:
Gary Talent 2022-05-18 21:15:11 -05:00
parent 71f6364ea3
commit 6854e658a0
12 changed files with 158 additions and 52 deletions

View File

@ -18,6 +18,7 @@ if(NOT NOSTALGIA_BUILD_TYPE STREQUAL "GBA")
${OPENGL_gl_LIBRARY}
glfw
imgui
OxEvent
NostalgiaGlUtils
)
else()

View File

@ -5,6 +5,7 @@
#pragma once
#include <ox/model/typenamecatcher.hpp>
#include <ox/event/signal.hpp>
#include <ox/fs/fs.hpp>
#include <ox/std/hashmap.hpp>
#include <ox/std/utility.hpp>
@ -27,6 +28,8 @@ struct AssetContainer {
mutable int m_references = 0;
public:
ox::Signal<ox::Error()> updated;
template<class... Args>
explicit constexpr AssetContainer(Args&&... args): m_obj(ox::forward<Args>(args)...) {
}
@ -39,6 +42,14 @@ struct AssetContainer {
return &m_obj;
}
constexpr void set(T &&val) {
m_obj = std::move(val);
}
constexpr void set(const T &val) {
m_obj = val;
}
protected:
constexpr void incRefs() const noexcept {
++m_references;
@ -55,27 +66,20 @@ struct AssetContainer {
};
template<typename T>
class AssetRef {
class AssetRef: public ox::SignalHandler {
private:
const AssetContainer<T> *m_ctr = nullptr;
public:
explicit constexpr AssetRef(const AssetContainer<T> *c = nullptr) noexcept: m_ctr(c) {
}
ox::Signal<ox::Error()> updated;
constexpr AssetRef(const AssetRef &h) noexcept {
m_ctr = h.m_ctr;
if (m_ctr) {
m_ctr->incRefs();
}
}
explicit constexpr AssetRef(const AssetContainer<T> *c = nullptr) noexcept;
constexpr AssetRef(AssetRef &&h) noexcept {
m_ctr = h.m_ctr;
h.m_ctr = nullptr;
}
constexpr AssetRef(const AssetRef &h) noexcept;
~AssetRef() noexcept {
constexpr AssetRef(AssetRef &&h) noexcept;
~AssetRef() noexcept override {
if (m_ctr) {
m_ctr->decRefs();
}
@ -103,8 +107,10 @@ class AssetRef {
}
if (m_ctr) {
m_ctr->decRefs();
oxIgnoreError(m_ctr->updated.disconnectObject(this));
}
m_ctr = h.m_ctr;
m_ctr->updated.connect(&updated, &ox::Signal<ox::Error()>::emitCheckError);
if (m_ctr) {
m_ctr->incRefs();
}
@ -115,7 +121,12 @@ class AssetRef {
if (this == &h) {
return *this;
}
if (m_ctr) {
m_ctr->decRefs();
oxIgnoreError(m_ctr->updated.disconnectObject(this));
}
m_ctr = h.m_ctr;
m_ctr->updated.connect(this, &AssetRef::emitUpdated);
h.m_ctr = nullptr;
return *this;
}
@ -124,8 +135,36 @@ class AssetRef {
return m_ctr;
}
private:
constexpr ox::Error emitUpdated() const noexcept {
updated.emit();
return OxError(0);
}
};
template<typename T>
constexpr AssetRef<T>::AssetRef(const AssetContainer<T> *c) noexcept: m_ctr(c) {
if (c) {
c->updated.connect(this, &AssetRef::emitUpdated);
}
}
template<typename T>
constexpr AssetRef<T>::AssetRef(const AssetRef &h) noexcept {
m_ctr = h.m_ctr;
if (m_ctr) {
m_ctr->updated.connect(this, &AssetRef::emitUpdated);
m_ctr->incRefs();
}
}
template<typename T>
constexpr AssetRef<T>::AssetRef(AssetRef &&h) noexcept {
m_ctr = h.m_ctr;
m_ctr->updated.connect(this, &AssetRef::emitUpdated);
h.m_ctr = nullptr;
}
class AssetManager {
private:
class AssetTypeManagerBase {
@ -148,7 +187,24 @@ class AssetManager {
}
ox::Result<AssetRef<T>> setAsset(const ox::String &path, const T &obj) noexcept {
auto &p = m_cache[path] = ox::make_unique<AssetContainer<T>>(obj);
auto &p = m_cache[path];
if (!p) {
p = ox::make_unique<AssetContainer<T>>(obj);
} else {
p->set(obj);
p->updated.emit();
}
return AssetRef<T>(p.get());
}
ox::Result<AssetRef<T>> setAsset(const ox::String &path, T &&obj) noexcept {
auto &p = m_cache[path];
if (!p) {
p = ox::make_unique<AssetContainer<T>>(obj);
} else {
p->set(std::move(obj));
p->updated.emit();
}
return AssetRef<T>(p.get());
}

View File

@ -150,7 +150,7 @@ void TileSheetEditorImGui::draw(core::Context*) noexcept {
m_subsheetEditor.draw();
}
void TileSheetEditorImGui::drawSubsheetSelector(TileSheet::SubSheet *subsheet, TileSheet::SubSheetIdx *path) noexcept {
void TileSheetEditorImGui::drawSubsheetSelector(TileSheet::SubSheet *subsheet, TileSheet::SubSheetIdx *path) {
ImGui::TableNextRow(0, 5);
using Str = ox::BasicString<100>;
auto pathStr = ox::join<Str>("##", *path).value;
@ -305,7 +305,7 @@ ox::Error TileSheetEditorImGui::updateActiveSubsheet(const ox::String &name, int
return model()->updateSubsheet(model()->activeSubSheetIdx(), name, cols, rows);
}
ox::Error TileSheetEditorImGui::markUnsavedChanges(int) noexcept {
ox::Error TileSheetEditorImGui::markUnsavedChanges(const studio::UndoCommand*) noexcept {
setUnsavedChanges(true);
return OxError(0);
}

View File

@ -74,7 +74,7 @@ class TileSheetEditorImGui: public studio::BaseEditor {
void draw(core::Context*) noexcept override;
void drawSubsheetSelector(TileSheet::SubSheet*, TileSheet::SubSheetIdx *path) noexcept;
void drawSubsheetSelector(TileSheet::SubSheet*, TileSheet::SubSheetIdx *path);
studio::UndoStack *undoStack() noexcept final;
@ -114,7 +114,7 @@ class TileSheetEditorImGui: public studio::BaseEditor {
private:
ox::Error updateAfterClicked() noexcept;
ox::Error markUnsavedChanges(int) noexcept;
ox::Error markUnsavedChanges(const studio::UndoCommand*) noexcept;
};

View File

@ -73,7 +73,13 @@ constexpr bool operator==(int i, CommandId c) noexcept {
return static_cast<int>(c) == i;
}
class DrawCommand: public studio::UndoCommand {
class TileSheetCommand: public studio::UndoCommand {
public:
[[nodiscard]]
virtual const TileSheet::SubSheetIdx &subsheetIdx() const noexcept = 0;
};
class DrawCommand: public TileSheetCommand {
private:
struct Change {
uint32_t idx = 0;
@ -150,10 +156,15 @@ class DrawCommand: public studio::UndoCommand {
return static_cast<int>(CommandId::Draw);
}
[[nodiscard]]
const TileSheet::SubSheetIdx &subsheetIdx() const noexcept override {
return m_subSheetIdx;
}
};
template<CommandId CommandId>
class CutPasteCommand: public studio::UndoCommand {
class CutPasteCommand: public TileSheetCommand {
private:
struct Change {
uint32_t idx = 0;
@ -202,9 +213,14 @@ class CutPasteCommand: public studio::UndoCommand {
return static_cast<int>(CommandId);
}
[[nodiscard]]
const TileSheet::SubSheetIdx &subsheetIdx() const noexcept override {
return m_subSheetIdx;
}
};
class AddSubSheetCommand: public studio::UndoCommand {
class AddSubSheetCommand: public TileSheetCommand {
private:
TileSheet *m_img = nullptr;
TileSheet::SubSheetIdx m_parentIdx;
@ -261,9 +277,14 @@ class AddSubSheetCommand: public studio::UndoCommand {
return static_cast<int>(CommandId::AddSubSheet);
}
[[nodiscard]]
const TileSheet::SubSheetIdx &subsheetIdx() const noexcept override {
return m_parentIdx;
}
};
class RmSubSheetCommand: public studio::UndoCommand {
class RmSubSheetCommand: public TileSheetCommand {
private:
TileSheet *m_img = nullptr;
TileSheet::SubSheetIdx m_idx;
@ -296,9 +317,14 @@ class RmSubSheetCommand: public studio::UndoCommand {
return static_cast<int>(CommandId::RmSubSheet);
}
[[nodiscard]]
const TileSheet::SubSheetIdx &subsheetIdx() const noexcept override {
return m_idx;
}
};
class UpdateSubSheetCommand: public studio::UndoCommand {
class UpdateSubSheetCommand: public TileSheetCommand {
private:
TileSheet *m_img = nullptr;
TileSheet::SubSheetIdx m_idx;
@ -336,16 +362,23 @@ class UpdateSubSheetCommand: public studio::UndoCommand {
return static_cast<int>(CommandId::UpdateSubSheet);
}
[[nodiscard]]
const TileSheet::SubSheetIdx &subsheetIdx() const noexcept override {
return m_idx;
}
};
class PaletteChangeCommand: public studio::UndoCommand {
class PaletteChangeCommand: public TileSheetCommand {
private:
TileSheet::SubSheetIdx m_idx;
TileSheet *m_img = nullptr;
ox::FileAddress m_oldPalette;
ox::FileAddress m_newPalette;
public:
constexpr PaletteChangeCommand(TileSheet *img, const ox::String &newPalette) noexcept {
constexpr PaletteChangeCommand(const TileSheet::SubSheetIdx &idx, TileSheet *img, const ox::String &newPalette) noexcept {
m_idx = idx;
m_img = img;
m_oldPalette = m_img->defaultPalette;
m_newPalette = newPalette;
@ -364,6 +397,11 @@ class PaletteChangeCommand: public studio::UndoCommand {
return static_cast<int>(CommandId::PaletteChange);
}
[[nodiscard]]
const TileSheet::SubSheetIdx &subsheetIdx() const noexcept override {
return m_idx;
}
};
@ -373,7 +411,8 @@ TileSheetEditorModel::TileSheetEditorModel(Context *ctx, const ox::String &path)
oxRequireT(img, readObj<TileSheet>(ctx, path.c_str()));
m_img = *img;
oxThrowError(readObj<Palette>(ctx, m_img.defaultPalette).moveTo(&m_pal));
m_undoStack.changeTriggered.connect(this, &TileSheetEditorModel::markUpdated);
m_pal.updated.connect(this, &TileSheetEditorModel::markUpdated);
m_undoStack.changeTriggered.connect(this, &TileSheetEditorModel::markUpdatedCmdId);
}
void TileSheetEditorModel::cut() {
@ -431,7 +470,7 @@ const ox::FileAddress &TileSheetEditorModel::palPath() const noexcept {
}
ox::Error TileSheetEditorModel::setPalette(const ox::String &path) noexcept {
pushCommand(new PaletteChangeCommand(&m_img, path));
pushCommand(new PaletteChangeCommand(activeSubSheetIdx(), &m_img, path));
return OxError(0);
}
@ -526,18 +565,12 @@ bool TileSheetEditorModel::updated() const noexcept {
return m_updated;
}
ox::Error TileSheetEditorModel::markUpdated(int cmdId) noexcept {
ox::Error TileSheetEditorModel::markUpdatedCmdId(const studio::UndoCommand *cmd) noexcept {
m_updated = true;
const auto cmdId = cmd->commandId();
switch (static_cast<CommandId>(cmdId)) {
case CommandId::AddSubSheet:
case CommandId::RmSubSheet: {
// make sure the current active SubSheet is still valid
auto idx = m_img.validateSubSheetIdx(m_activeSubsSheetIdx);
if (idx != m_activeSubsSheetIdx) {
setActiveSubsheet(idx);
}
break;
}
case CommandId::RmSubSheet:
case CommandId::Cut:
case CommandId::Draw:
case CommandId::Paste:
@ -547,6 +580,16 @@ ox::Error TileSheetEditorModel::markUpdated(int cmdId) noexcept {
oxReturnError(readObj<Palette>(m_ctx, m_img.defaultPalette.getPath().value).moveTo(&m_pal));
break;
}
auto tsCmd = dynamic_cast<const TileSheetCommand*>(cmd);
auto idx = m_img.validateSubSheetIdx(tsCmd->subsheetIdx());
if (idx != m_activeSubsSheetIdx) {
setActiveSubsheet(idx);
}
return OxError(0);
}
ox::Error TileSheetEditorModel::markUpdated() noexcept {
m_updated = true;
return OxError(0);
}

View File

@ -94,7 +94,9 @@ class TileSheetEditorModel: public ox::SignalHandler {
[[nodiscard]]
bool updated() const noexcept;
ox::Error markUpdated(int) noexcept;
ox::Error markUpdatedCmdId(const studio::UndoCommand *cmd) noexcept;
ox::Error markUpdated() noexcept;
void ackUpdate() noexcept;

View File

@ -79,6 +79,11 @@ bool TileSheetEditorView::updated() const noexcept {
return m_updated || m_model.updated();
}
ox::Error TileSheetEditorView::markUpdated() noexcept {
m_updated = true;
return OxError(0);
}
void TileSheetEditorView::ackUpdate() noexcept {
m_updated = false;
m_model.ackUpdate();

View File

@ -100,6 +100,8 @@ class TileSheetEditorView: public ox::SignalHandler {
[[nodiscard]]
bool updated() const noexcept;
ox::Error markUpdated() noexcept;
void ackUpdate() noexcept;
private:

View File

@ -93,7 +93,7 @@ Editor::Editor() noexcept {
m_undoStack.changeTriggered.connect(this, &Editor::markUnsavedChanges);
}
ox::Error Editor::markUnsavedChanges(int) noexcept {
ox::Error Editor::markUnsavedChanges(const UndoCommand*) noexcept {
setUnsavedChanges(true);
return OxError(0);
}

View File

@ -123,7 +123,7 @@ class Editor: public studio::BaseEditor {
}
private:
ox::Error markUnsavedChanges(int) noexcept;
ox::Error markUnsavedChanges(const UndoCommand*) noexcept;
};

View File

@ -14,10 +14,9 @@ void UndoStack::push(UndoCommand *cmd) noexcept {
for (const auto i = m_stackIdx; i < m_stack.size();) {
oxIgnoreError(m_stack.erase(i));
}
auto cmdId = cmd->commandId();
cmd->redo();
redoTriggered.emit(cmdId);
changeTriggered.emit(cmdId);
redoTriggered.emit(cmd);
changeTriggered.emit(cmd);
if (!m_stack.size() || !m_stack.back().value->mergeWith(cmd)) {
m_stack.emplace_back(cmd);
++m_stackIdx;
@ -29,20 +28,18 @@ void UndoStack::push(UndoCommand *cmd) noexcept {
void UndoStack::redo() noexcept {
if (m_stackIdx < m_stack.size()) {
auto &c = m_stack[m_stackIdx++];
auto cmdId = c->commandId();
c->redo();
redoTriggered.emit(cmdId);
changeTriggered.emit(cmdId);
redoTriggered.emit(c.get());
changeTriggered.emit(c.get());
}
}
void UndoStack::undo() noexcept {
if (m_stackIdx) {
auto &c = m_stack[--m_stackIdx];
auto cmdId = c->commandId();
c->undo();
undoTriggered.emit(cmdId);
changeTriggered.emit(cmdId);
undoTriggered.emit(c.get());
changeTriggered.emit(c.get());
}
}

View File

@ -43,9 +43,9 @@ class UndoStack {
return m_stackIdx;
}
ox::Signal<ox::Error(int)> redoTriggered;
ox::Signal<ox::Error(int)> undoTriggered;
ox::Signal<ox::Error(int)> changeTriggered;
ox::Signal<ox::Error(const studio::UndoCommand*)> redoTriggered;
ox::Signal<ox::Error(const studio::UndoCommand*)> undoTriggered;
ox::Signal<ox::Error(const studio::UndoCommand*)> changeTriggered;
};
}