From 81aa5ad999e448a4a864526f02ef9e6b68856144 Mon Sep 17 00:00:00 2001 From: ajreckof <66184050+ajreckof@users.noreply.github.com> Date: Fri, 2 Jun 2023 23:03:39 +0200 Subject: [PATCH] add backward_undo_ops as property for action --- core/object/undo_redo.cpp | 16 +++++++++--- core/object/undo_redo.h | 5 ++-- doc/classes/EditorUndoRedoManager.xml | 2 ++ doc/classes/UndoRedo.xml | 35 +++++++++++++++++++++++++-- editor/editor_undo_redo_manager.cpp | 13 +++++----- editor/editor_undo_redo_manager.h | 5 ++-- 6 files changed, 61 insertions(+), 15 deletions(-) diff --git a/core/object/undo_redo.cpp b/core/object/undo_redo.cpp index 077929351e85..17d9eaff0b8e 100644 --- a/core/object/undo_redo.cpp +++ b/core/object/undo_redo.cpp @@ -80,14 +80,14 @@ bool UndoRedo::_redo(bool p_execute) { return true; } -void UndoRedo::create_action(const String &p_name, MergeMode p_mode) { +void UndoRedo::create_action(const String &p_name, MergeMode p_mode, bool p_backward_undo_ops) { uint64_t ticks = OS::get_singleton()->get_ticks_msec(); if (action_level == 0) { _discard_redo(); // Check if the merge operation is valid - if (p_mode != MERGE_DISABLE && actions.size() && actions[actions.size() - 1].name == p_name && actions[actions.size() - 1].last_tick + 800 > ticks) { + if (p_mode != MERGE_DISABLE && actions.size() && actions[actions.size() - 1].name == p_name && actions[actions.size() - 1].backward_undo_ops == p_backward_undo_ops && actions[actions.size() - 1].last_tick + 800 > ticks) { current_action = actions.size() - 2; if (p_mode == MERGE_ENDS) { @@ -108,12 +108,18 @@ void UndoRedo::create_action(const String &p_name, MergeMode p_mode) { actions.write[actions.size() - 1].last_tick = ticks; + // Revert reverse from previous commit. + if (actions[actions.size() - 1].backward_undo_ops) { + actions.write[actions.size() - 1].undo_ops.reverse(); + } + merge_mode = p_mode; merging = true; } else { Action new_action; new_action.name = p_name; new_action.last_tick = ticks; + new_action.backward_undo_ops = p_backward_undo_ops; actions.push_back(new_action); merge_mode = MERGE_DISABLE; @@ -292,6 +298,10 @@ void UndoRedo::commit_action(bool p_execute) { merging = false; } + if (actions[actions.size() - 1].backward_undo_ops) { + actions.write[actions.size() - 1].undo_ops.reverse(); + } + committing++; _redo(p_execute); // perform action committing--; @@ -458,7 +468,7 @@ UndoRedo::~UndoRedo() { } void UndoRedo::_bind_methods() { - ClassDB::bind_method(D_METHOD("create_action", "name", "merge_mode"), &UndoRedo::create_action, DEFVAL(MERGE_DISABLE)); + ClassDB::bind_method(D_METHOD("create_action", "name", "merge_mode", "backward_undo_ops"), &UndoRedo::create_action, DEFVAL(MERGE_DISABLE), DEFVAL(false)); ClassDB::bind_method(D_METHOD("commit_action", "execute"), &UndoRedo::commit_action, DEFVAL(true)); ClassDB::bind_method(D_METHOD("is_committing_action"), &UndoRedo::is_committing_action); diff --git a/core/object/undo_redo.h b/core/object/undo_redo.h index 5dc9f2966c05..2ee17867f239 100644 --- a/core/object/undo_redo.h +++ b/core/object/undo_redo.h @@ -72,7 +72,8 @@ private: String name; List do_ops; List undo_ops; - uint64_t last_tick; + uint64_t last_tick = 0; + bool backward_undo_ops = false; }; Vector actions; @@ -102,7 +103,7 @@ protected: static void _bind_methods(); public: - void create_action(const String &p_name = "", MergeMode p_mode = MERGE_DISABLE); + void create_action(const String &p_name = "", MergeMode p_mode = MERGE_DISABLE, bool p_backward_undo_ops = false); void add_do_method(const Callable &p_callable); void add_undo_method(const Callable &p_callable); diff --git a/doc/classes/EditorUndoRedoManager.xml b/doc/classes/EditorUndoRedoManager.xml index aaf74ebbe0b4..553e36bd42c8 100644 --- a/doc/classes/EditorUndoRedoManager.xml +++ b/doc/classes/EditorUndoRedoManager.xml @@ -80,10 +80,12 @@ + Create a new action. After this is called, do all your calls to [method add_do_method], [method add_undo_method], [method add_do_property], and [method add_undo_property], then commit the action with [method commit_action]. The way actions are merged is dictated by the [param merge_mode] argument. See [enum UndoRedo.MergeMode] for details. If [param custom_context] object is provided, it will be used for deducing target history (instead of using the first operation). + The way undo operation are ordered in actions is dictated by [param backward_undo_ops]. When [param backward_undo_ops] is [code]false[/code] undo option are ordered in the same order they were added. Which means the first operation to be added will be the first to be undone. diff --git a/doc/classes/UndoRedo.xml b/doc/classes/UndoRedo.xml index 9b9bf7f5695d..d2b0bbb0ca55 100644 --- a/doc/classes/UndoRedo.xml +++ b/doc/classes/UndoRedo.xml @@ -56,9 +56,38 @@ } [/csharp] [/codeblocks] - [method create_action], [method add_do_method], [method add_undo_method], [method add_do_property], [method add_undo_property], and [method commit_action] should be called one after the other, like in the example. Not doing so could lead to crashes. - If you don't need to register a method, you can leave [method add_do_method] and [method add_undo_method] out; the same goes for properties. You can also register more than one method/property in the order they should run. + Before calling any of the [code]add_(un)do_*[/code] methods, you need to first call [method create_action]. Afterwards you need to call [method commit_action]. + If you don't need to register a method, you can leave [method add_do_method] and [method add_undo_method] out; the same goes for properties. You can also register more than one method/property. If you are making an [EditorPlugin] and want to integrate into the editor's undo history, use [EditorUndoRedoManager] instead. + If you are registering multiple properties/method which depend on one another, be aware that by default undo operation are called in the same order they have been added. Therefore instead of grouping do operation with their undo operations it is better to group do on one side and undo on the other as shown below. + [codeblocks] + [gdscript] + undo_redo.create_action("Add object") + + # DO + undo_redo.add_do_method(_create_object) + undo_redo.add_do_method(_add_object_to_singleton) + + # UNDO + undo_redo.add_undo_method(_remove_object_from_singleton) + undo_redo.add_undo_method(_destroy_that_object) + + undo_redo.commit_action() + [/gdscript] + [csharp] + _undo_redo.CreateAction("Add object"); + + // DO + _undo_redo.AddDoMethod(new Callable(this, MethodName.CreateObject)); + _undo_redo.AddDoMethod(new Callable(this, MethodName.AddObjectToSingleton)); + + // UNDO + _undo_redo.AddUndoMethod(new Callable(this, MethodName.RemoveObjectFromSingleton)); + _undo_redo.AddUndoMethod(new Callable(this, MethodName.DestroyThatObject)); + + _undo_redo.CommitAction(); + [/csharp] + [/codeblocks] @@ -144,9 +173,11 @@ + Create a new action. After this is called, do all your calls to [method add_do_method], [method add_undo_method], [method add_do_property], and [method add_undo_property], then commit the action with [method commit_action]. The way actions are merged is dictated by [param merge_mode]. See [enum MergeMode] for details. + The way undo operation are ordered in actions is dictated by [param backward_undo_ops]. When [param backward_undo_ops] is [code]false[/code] undo option are ordered in the same order they were added. Which means the first operation to be added will be the first to be undone. diff --git a/editor/editor_undo_redo_manager.cpp b/editor/editor_undo_redo_manager.cpp index facd1fd6d3ac..fd2d51be32ca 100644 --- a/editor/editor_undo_redo_manager.cpp +++ b/editor/editor_undo_redo_manager.cpp @@ -110,13 +110,13 @@ EditorUndoRedoManager::History &EditorUndoRedoManager::get_history_for_object(Ob History &history = get_or_create_history(history_id); if (pending_action.history_id == INVALID_HISTORY) { pending_action.history_id = history_id; - history.undo_redo->create_action(pending_action.action_name, pending_action.merge_mode); + history.undo_redo->create_action(pending_action.action_name, pending_action.merge_mode, pending_action.backward_undo_ops); } return history; } -void EditorUndoRedoManager::create_action_for_history(const String &p_name, int p_history_id, UndoRedo::MergeMode p_mode) { +void EditorUndoRedoManager::create_action_for_history(const String &p_name, int p_history_id, UndoRedo::MergeMode p_mode, bool p_backward_undo_ops) { if (pending_action.history_id != INVALID_HISTORY) { // Nested action. p_history_id = pending_action.history_id; @@ -124,17 +124,18 @@ void EditorUndoRedoManager::create_action_for_history(const String &p_name, int pending_action.action_name = p_name; pending_action.timestamp = OS::get_singleton()->get_unix_time(); pending_action.merge_mode = p_mode; + pending_action.backward_undo_ops = p_backward_undo_ops; } if (p_history_id != INVALID_HISTORY) { pending_action.history_id = p_history_id; History &history = get_or_create_history(p_history_id); - history.undo_redo->create_action(pending_action.action_name, pending_action.merge_mode); + history.undo_redo->create_action(pending_action.action_name, pending_action.merge_mode, pending_action.backward_undo_ops); } } -void EditorUndoRedoManager::create_action(const String &p_name, UndoRedo::MergeMode p_mode, Object *p_custom_context) { - create_action_for_history(p_name, INVALID_HISTORY, p_mode); +void EditorUndoRedoManager::create_action(const String &p_name, UndoRedo::MergeMode p_mode, Object *p_custom_context, bool p_backward_undo_ops) { + create_action_for_history(p_name, INVALID_HISTORY, p_mode, p_backward_undo_ops); if (p_custom_context) { // This assigns history to pending action. @@ -487,7 +488,7 @@ EditorUndoRedoManager::History *EditorUndoRedoManager::_get_newest_undo() { } void EditorUndoRedoManager::_bind_methods() { - ClassDB::bind_method(D_METHOD("create_action", "name", "merge_mode", "custom_context"), &EditorUndoRedoManager::create_action, DEFVAL(UndoRedo::MERGE_DISABLE), DEFVAL((Object *)nullptr)); + ClassDB::bind_method(D_METHOD("create_action", "name", "merge_mode", "custom_context", "backward_undo_ops"), &EditorUndoRedoManager::create_action, DEFVAL(UndoRedo::MERGE_DISABLE), DEFVAL((Object *)nullptr), DEFVAL(false)); ClassDB::bind_method(D_METHOD("commit_action", "execute"), &EditorUndoRedoManager::commit_action, DEFVAL(true)); ClassDB::bind_method(D_METHOD("is_committing_action"), &EditorUndoRedoManager::is_committing_action); diff --git a/editor/editor_undo_redo_manager.h b/editor/editor_undo_redo_manager.h index 10daeae80780..effa36a87c48 100644 --- a/editor/editor_undo_redo_manager.h +++ b/editor/editor_undo_redo_manager.h @@ -52,6 +52,7 @@ public: double timestamp = 0; String action_name; UndoRedo::MergeMode merge_mode = UndoRedo::MERGE_DISABLE; + bool backward_undo_ops = false; }; struct History { @@ -79,8 +80,8 @@ public: int get_history_id_for_object(Object *p_object) const; History &get_history_for_object(Object *p_object); - void create_action_for_history(const String &p_name, int p_history_id, UndoRedo::MergeMode p_mode = UndoRedo::MERGE_DISABLE); - void create_action(const String &p_name = "", UndoRedo::MergeMode p_mode = UndoRedo::MERGE_DISABLE, Object *p_custom_context = nullptr); + void create_action_for_history(const String &p_name, int p_history_id, UndoRedo::MergeMode p_mode = UndoRedo::MERGE_DISABLE, bool p_backward_undo_ops = false); + void create_action(const String &p_name = "", UndoRedo::MergeMode p_mode = UndoRedo::MERGE_DISABLE, Object *p_custom_context = nullptr, bool p_backward_undo_ops = false); void add_do_methodp(Object *p_object, const StringName &p_method, const Variant **p_args, int p_argcount); void add_undo_methodp(Object *p_object, const StringName &p_method, const Variant **p_args, int p_argcount);