Add a way to force undo/redo operations to be kept in MERGE_ENDS mode

This commit is contained in:
Gilles Roudière 2021-10-12 14:03:05 +02:00
parent 0fd50ff217
commit 1be00864b7
3 changed files with 65 additions and 13 deletions

View file

@ -32,6 +32,7 @@
#include "core/io/resource.h"
#include "core/os/os.h"
#include "core/templates/local_vector.h"
void UndoRedo::_discard_redo() {
if (current_action == actions.size() - 1) {
@ -85,10 +86,17 @@ void UndoRedo::create_action(const String &p_name, MergeMode p_mode) {
current_action = actions.size() - 2;
if (p_mode == MERGE_ENDS) {
// Clear all do ops from last action, and delete all object references
List<Operation>::Element *E = actions.write[current_action + 1].do_ops.front();
// Clear all do ops from last action if they are not forced kept
LocalVector<List<Operation>::Element *> to_remove;
for (List<Operation>::Element *E = actions.write[current_action + 1].do_ops.front(); E; E = E->next()) {
if (!E->get().force_keep_in_merge_ends) {
to_remove.push_back(E);
}
}
while (E) {
for (unsigned int i = 0; i < to_remove.size(); i++) {
List<Operation>::Element *E = to_remove[i];
// Delete all object references
if (E->get().type == Operation::TYPE_REFERENCE) {
Object *obj = ObjectDB::get_instance(E->get().object);
@ -96,27 +104,34 @@ void UndoRedo::create_action(const String &p_name, MergeMode p_mode) {
memdelete(obj);
}
}
E = E->next();
actions.write[current_action + 1].do_ops.pop_front();
String s = "removed " + E->get().name + ": ";
for (int j = 0; j < VARIANT_ARG_MAX; j++) {
if (E->get().args[j].get_type() == Variant::NIL) {
break;
}
s += String(E->get().args[j]);
}
print_line(s);
E->erase();
}
}
actions.write[actions.size() - 1].last_tick = ticks;
merge_mode = p_mode;
merging = true;
} else {
Action new_action;
new_action.name = p_name;
new_action.last_tick = ticks;
actions.push_back(new_action);
merge_mode = MERGE_DISABLE;
}
merge_mode = p_mode;
}
action_level++;
force_keep_in_merge_ends = false;
}
void UndoRedo::add_do_method(Object *p_object, const StringName &p_method, VARIANT_ARG_DECLARE) {
@ -146,7 +161,7 @@ void UndoRedo::add_undo_method(Object *p_object, const StringName &p_method, VAR
ERR_FAIL_COND((current_action + 1) >= actions.size());
// No undo if the merge mode is MERGE_ENDS
if (merge_mode == MERGE_ENDS) {
if (!force_keep_in_merge_ends && merge_mode == MERGE_ENDS) {
return;
}
@ -157,6 +172,7 @@ void UndoRedo::add_undo_method(Object *p_object, const StringName &p_method, VAR
}
undo_op.type = Operation::TYPE_METHOD;
undo_op.force_keep_in_merge_ends = force_keep_in_merge_ends;
undo_op.name = p_method;
for (int i = 0; i < VARIANT_ARG_MAX; i++) {
@ -187,7 +203,7 @@ void UndoRedo::add_undo_property(Object *p_object, const StringName &p_property,
ERR_FAIL_COND((current_action + 1) >= actions.size());
// No undo if the merge mode is MERGE_ENDS
if (merge_mode == MERGE_ENDS) {
if (!force_keep_in_merge_ends && merge_mode == MERGE_ENDS) {
return;
}
@ -198,6 +214,7 @@ void UndoRedo::add_undo_property(Object *p_object, const StringName &p_property,
}
undo_op.type = Operation::TYPE_PROPERTY;
undo_op.force_keep_in_merge_ends = force_keep_in_merge_ends;
undo_op.name = p_property;
undo_op.args[0] = p_value;
actions.write[current_action + 1].undo_ops.push_back(undo_op);
@ -223,7 +240,7 @@ void UndoRedo::add_undo_reference(Object *p_object) {
ERR_FAIL_COND((current_action + 1) >= actions.size());
// No undo if the merge mode is MERGE_ENDS
if (merge_mode == MERGE_ENDS) {
if (!force_keep_in_merge_ends && merge_mode == MERGE_ENDS) {
return;
}
@ -234,9 +251,24 @@ void UndoRedo::add_undo_reference(Object *p_object) {
}
undo_op.type = Operation::TYPE_REFERENCE;
undo_op.force_keep_in_merge_ends = force_keep_in_merge_ends;
actions.write[current_action + 1].undo_ops.push_back(undo_op);
}
void UndoRedo::start_force_keep_in_merge_ends() {
ERR_FAIL_COND(action_level <= 0);
ERR_FAIL_COND((current_action + 1) >= actions.size());
force_keep_in_merge_ends = true;
}
void UndoRedo::end_force_keep_in_merge_ends() {
ERR_FAIL_COND(action_level <= 0);
ERR_FAIL_COND((current_action + 1) >= actions.size());
force_keep_in_merge_ends = false;
}
void UndoRedo::_pop_history_tail() {
_discard_redo();
@ -538,6 +570,9 @@ void UndoRedo::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_do_reference", "object"), &UndoRedo::add_do_reference);
ClassDB::bind_method(D_METHOD("add_undo_reference", "object"), &UndoRedo::add_undo_reference);
ClassDB::bind_method(D_METHOD("start_force_keep_in_merge_ends"), &UndoRedo::start_force_keep_in_merge_ends);
ClassDB::bind_method(D_METHOD("end_force_keep_in_merge_ends"), &UndoRedo::end_force_keep_in_merge_ends);
ClassDB::bind_method(D_METHOD("get_history_count"), &UndoRedo::get_history_count);
ClassDB::bind_method(D_METHOD("get_current_action"), &UndoRedo::get_current_action);
ClassDB::bind_method(D_METHOD("get_action_name", "id"), &UndoRedo::get_action_name);

View file

@ -61,6 +61,7 @@ private:
};
Type type;
bool force_keep_in_merge_ends;
Ref<RefCounted> ref;
ObjectID object;
StringName name;
@ -76,6 +77,7 @@ private:
Vector<Action> actions;
int current_action = -1;
bool force_keep_in_merge_ends = false;
int action_level = 0;
MergeMode merge_mode = MERGE_DISABLE;
bool merging = false;
@ -109,6 +111,9 @@ public:
void add_do_reference(Object *p_object);
void add_undo_reference(Object *p_object);
void start_force_keep_in_merge_ends();
void end_force_keep_in_merge_ends();
bool is_committing_action() const;
void commit_action(bool p_execute = true);

View file

@ -134,6 +134,12 @@
The way actions are merged is dictated by the [code]merge_mode[/code] argument. See [enum MergeMode] for details.
</description>
</method>
<method name="end_force_keep_in_merge_ends">
<return type="void" />
<description>
Stops marking operations as to be processed even if the action gets merged with another in the [constant MERGE_ENDS] mode. See [method start_force_keep_in_merge_ends].
</description>
</method>
<method name="get_action_name">
<return type="String" />
<argument index="0" name="id" type="int" />
@ -190,6 +196,12 @@
Redo the last action.
</description>
</method>
<method name="start_force_keep_in_merge_ends">
<return type="void" />
<description>
Marks the next "do" and "undo" operations to be processed even if the action gets merged with another in the [constant MERGE_ENDS] mode. Return to normal operation using [method end_force_keep_in_merge_ends].
</description>
</method>
<method name="undo">
<return type="bool" />
<description>
@ -209,7 +221,7 @@
Makes "do"/"undo" operations stay in separate actions.
</constant>
<constant name="MERGE_ENDS" value="1" enum="MergeMode">
Makes so that the action's "do" operation is from the first action created and the "undo" operation is from the last subsequent action with the same name.
Makes so that the action's "undo" operations are from the first action created and the "do" operations are from the last subsequent action with the same name.
</constant>
<constant name="MERGE_ALL" value="2" enum="MergeMode">
Makes subsequent actions with the same name be merged into one.