Browse Source

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

Gilles Roudière 3 years ago
parent
commit
1be00864b7
3 changed files with 65 additions and 13 deletions
  1. 47 12
      core/object/undo_redo.cpp
  2. 5 0
      core/object/undo_redo.h
  3. 13 1
      doc/classes/UndoRedo.xml

+ 47 - 12
core/object/undo_redo.cpp

@@ -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);

+ 5 - 0
core/object/undo_redo.h

@@ -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);
 

+ 13 - 1
doc/classes/UndoRedo.xml

@@ -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.