xyz thanks,
what about the "EditorPlugin" script ?
Ive made it using "add_custom_type". But the only way i know of on how to get the @tool node is in the handle(Object)function

Couldnt there be way to to get the node "add_custom_type" in the enter tree ? And pass the undoRendo there ?
I mean... Iam not doing anything in the "EditorPlugin", all the code is being done in the @tool node ?

  • xyz replied to this.

    jonSS Yes, the most convenient way is to inject the manager reference into the edited object in EditorPlugin::_handles(). Or declare a static reference in the edited object class and assign it once from EditorPlugin::_enter_tree()

    Alternatively you can name the plugin node and then each edited node can get the manager from it using something similar to the "hack" I posted above.

      xyz great thanks
      ill leave it has it is then in the _handles() function

      there is another problem...
      it seems the undo rendo is lost or doesnt change places when using diferent nodes

      If i use undo rendo on an "EditorInspectorPlugin" scene,
      it goes to the undo rendo on the other side ?

      inspector.gd

      #----//--ADD-itemList-layer-//----
      func _on_btn_add_layer_pressed():
      	var listSize = itemList.get_item_count();
      	var itemName = "layer " + str(listSize)
      	#-----------------undo-rendo-add-layer------------------------------
      	undoredo.create_action("tileDraw - add layer");
      	undoredo.add_do_method(self, "_do_add_layer", itemName, listSize);
      	undoredo.add_undo_method(self, "_undo_remove_layer", listSize);
      	undoredo.commit_action();
      	#-----------------------------------------------------------
      
      #---------//--UNDO-RENDO-ADD-LAYER-//--------------
      func _do_add_layer(layer_name: String, index: int):
      	itemList.add_item(layer_name)
      	PlugTileDrawNode2.layerName.append(layer_name);
      	PlugTileDrawNode2.layers.Lnum.append({});
      	PlugTileDrawNode2.layers.Lnum.resize(index + 1);
      	notify_property_list_changed()
      	
      
      func _undo_remove_layer(index: int):
      	if index >= 0 and index < itemList.get_item_count():
      		itemList.select(index-1, true);
      		_on_item_list_multi_selected(index-1, true);
      		itemList.remove_item(index)
      		PlugTileDrawNode2.layerName.remove_at(index)
      		PlugTileDrawNode2.layers.Lnum.remove_at(index)
      	
      	notify_property_list_changed()
      	pass;

      This happens when draw on layer / add layer / draw on layer / add layer...etc... the undo_rendo always goes into the tileDraw script

      • xyz replied to this.

        jonSS Editor undo queue is scene specific. Each edited scene has its own queue. That's why undo/redo commands are in the Scene menu 🙂. Read the docs for EditorUndoRedoManager class.

        If you want a node specific or otherwise independent system, use UndoRedo object. However this will not be tied with the editor undo queue and you'll have to add your own gui for issuing undo commands. This is likely not something you actually need here.

        Btw it's re-do, not re-ndo.

          xyz i dont know...
          this is the amount of code i have to do, just to merge ( the selected layers ) together into 1...
          it uses previous functions on item select, and previous vars... and runs remove layers at the end.

          I dont think the undo redo is supossed to fit in here ?
          wouldnt it just be simpler to just save everything inside a var, tileDraw.gd... inspector.gd, and use the undo redo to restore everything ?

          I just dont think the way of how it is, the undo rendo is supossed to deal with this ?

          • xyz replied to this.

            jonSS Again, it has nothing to do with undo system per se. This thread is meant as a tutorial on how it actually works.
            In your case your whole data architecture is likely not properly done. That's why you have problems conforming the whole thing to engine's undo system. So if you want/need to use that system, reorganize your plugin to play nice with it.

            From what I've seen so far in your code, you're not fully understanding how arrays/dictionaries work. Best to first entrench your knowledge about that in a simpler project. This is fundamental, so cursory understanding is not enough if you wish to make something more ambitious like a plugin.

            Btw just posting random excerpts of code form your project is again not a very good communication of the problem. For umpteenth time, isolate the problem into a fresh project, make a new thread about it.

            jonSS The thing with undo/redo for a complex object/action is that you need to have a function that does the operation and a function that undoes the operation, both changing the state of the object. How exactly to do this is closely knit with your data architecture. You typically need to store only necessary parameters to (un)perform the action. That's precisely what Godot's UndoRedo class helps you with. This is how undo is implemented in almost every app, it's not a Godot specific thing.

            To conclude, here's a bit more elaborate example that implements undo in a plugin that draws grided circles on a canvas item node. It's somewhat similar to your situation @jonSS only simpler, for clarity. the action is implemented in a proper way so the undo works between any number of drawable nodes in the scene. I won't go into any explanations as I think the code is self explanatory enough. If not, just study it harder 😃

            @tool
            class_name Drawable extends CanvasItem
            
            @export var color = Color.YELLOW
            var draw_list = {}
            
            func _draw():
            	for pos in draw_list.keys():
            		draw_circle(pos, 32, color, true)
            		
            func _get_property_list():
            	return [{"name": "draw_list", "type" : TYPE_DICTIONARY, "usage": PROPERTY_USAGE_STORAGE}] 
            @tool
            extends EditorPlugin
            
            var drawable = null # current selected node we draw on
            var stroke_draw_list = {} # current stroke draw list, used for undo
            
            func _handles(object):
            	drawable = object if object is Drawable else null
            	return object is Drawable
            
            func _forward_canvas_gui_input(e):
            	if not drawable or e is InputEventKey: 
            		return false
            	if e is InputEventMouseMotion and e.button_mask == MOUSE_BUTTON_MASK_LEFT:
            		add_to_current_stroke()
            	if e is InputEventMouseButton and e.button_index == MOUSE_BUTTON_LEFT and not e.is_pressed():
            		end_stroke()
            	return true
            		
            func add_to_current_stroke():
            	var position = snapped(Vector2i(drawable.get_local_mouse_position()), Vector2i(64, 64))
            	if not drawable.draw_list.has(position):
            		drawable.draw_list[position] = 1 # some dummy value, when drawing textures can be a texture reference
            		stroke_draw_list[position] = 1
            		drawable.queue_redraw()
            
            func end_stroke():
            	remove_from_draw_list(drawable, stroke_draw_list)
            	var undoredo = get_undo_redo()
            	undoredo.create_action("draw")
            	undoredo.add_do_method(self, "add_to_draw_list", drawable, stroke_draw_list.duplicate())
            	undoredo.add_undo_method(self, "remove_from_draw_list", drawable, stroke_draw_list.duplicate())
            	undoredo.commit_action()
            	stroke_draw_list.clear()
            	
            func add_to_draw_list(object, stroke):
            	object.draw_list.merge(stroke)
            	object.queue_redraw()
            	
            func remove_from_draw_list(object, stroke):
            	for pos in stroke.keys():
            		object.draw_list.erase(pos)
            	object.queue_redraw()

            Did you add all the code ?
            For me the undo redo doesnt work, changing the color replaces the color of all circles on screen
            I also had to use "class_name Drawable extends Node2D" instead of "CanvasItem" or i cant get node to show in the list

            • xyz replied to this.

              jonSS That's all the code. I never said the class will appear on the list of nodes. It won't because it inherits an abstract class. You can just attach the Drawable script to any existing canvas item node and it will work. But that all is beside the point. You can inherit any other canvas item class. Makes no difference for what the example is about.

              The color is for the whole node. It's just to differentiate between the nodes so we can see how undo properly functions with multiple nodes. Not meant to work as some kind of brush color and it's not included in the undoable action. Although that'd be easy to implement. I'm leaving it to you as a homework.

              I'm here in the business of demonstrating the basic key concepts in the most compact form, not finetuning the user experience 🙂

                xyz In the case of the "EditorInspectorPlugin" its diferent

                @tool
                extends EditorInspectorPlugin
                
                const inspect0 = preload("res://inspector/inspector.tscn");
                
                func _can_handle(object):
                	if object is tileDrawNode:
                		return true
                	return false
                
                func _parse_begin(object):
                	inspect1 = inspect0.instantiate();

                everytime you press the editor screen it triggres the ready function in "inspect1"... i only found out about it now,
                maybe the undoRedo history of that tscn gets reset

                • xyz replied to this.

                  jonSS Inspector plugin has nothing to do with undo. Undo happens with the data, not with the interface that displays it.

                  For some reason its reseting it... I dont have anything in the code that does that

                  If i use this:

                  tileDrawNode.gd

                  @tool
                  extends Node2D
                  #class_name tileDrawX1;
                  
                  func _process(_delta):
                  	
                  	if ( Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT) && mouseJustPress == false ):
                  		mouseJustPress = true;
                  		
                  		if ( drawText == null ):
                  			return;
                  		
                  		mouseGroup.Fnum.clear();
                  		#-----------------------------------------------------------
                  		#-----------------undo-rendo-draw-------------------------------
                  		undoredo.create_action("tileDraw - drawing Tiles");
                  		undoredo.add_undo_method(self, "remove_element", layers.Lnum[ layerPOS ].duplicate(true) );
                  		undoredo.add_do_method(self, "add_element", layers.Lnum[ layerPOS ].duplicate(true) );
                  		undoredo.commit_action();

                  I get 2 prints on the other side in the tscn the EditorInspectorPlugin spawns

                  tileDrawInspector.gd

                  @tool
                  extends EditorInspectorPlugin
                  
                  const inspect0 = preload("res://inspector/inspector.tscn");
                  
                  func _parse_begin(object):
                  	inspect1 = inspect0.instantiate();

                  this prints out 2 times everytime i mouse click on the editor, "inspector Ready"

                  inspector.gd

                  @tool
                  extends Control
                  
                  func _ready():
                  	print("inspector Ready")

                  Its the undoRedo block of code for some reason is deleting or restarting the inspector, i dont have anything else if i remove that block of code the inspctor.tscn ready function no longer prints ?

                  • xyz replied to this.

                    jonSS For some reason its reseting it

                    Who resets what? Remember that this is not a thread about your project. Have you managed to implement a simple undo system on your own in a fresh project following the model shown in my example? Do that and it'll make things a bit clearer. Don't just copypaste. Make your own variations. Add stuff to it. Do "what if" experiments with it. Tinker until you gain full understanding of how it works.

                    Again, the inspector has nothing to do with undo. If you're storing undoable data in an inspector then your architecture is not properly done. All undoable data needs to be on edited node's side. An inspector should only read that data and display it. Akin to classical model-view-controller design pattern.

                    Typically you'd implement something like update() or refresh() method in your inspector and call it every time you want to refresh what the inspector displays. In that method you'd read all the relevant data from the edited node and set the inspector's state accordingly. Everything that lives in the inspector should be totally disposable.

                    Also, not directly related, but all viewport input concerning the plugin (e.g. editing plugin supported nodes...) is best handled in EditorPlugin::_forward_*_gui_input(). See my example.

                      xyz I dont know man... You want to force your its working stuff, and its the other s guys fault, you start looking like an idiot.
                      I havent added anything to the inspector, about redo undo etc.. its on the plugIN node.
                      I havent done the redoUndo, all i know is it reset "EditorInspectorPlugin" ( func _parse_begin(object) ) and everything along with it "inspect1 " etc...
                      Ive made examples and tested it, and cant do anything to prevent those scripts startUp functions from running...
                      If there are idiots that believe in what you say, good congralutations reason is on your side... but it doesnt work... shared believed doesnt make anything work..

                        jonSS I see. Good luck with your future coding endeavors.

                        For the record, here's an example undo project that contains only my above code.

                        Lo and behold - it works! 😃 Who would've thunk it? 🐱

                        undo-example.zip
                        3kB

                          xyz great !!! what iam supossed to do now ? create a new forum account and use the Tor Browser ?

                          you need to add "EditorInspectorPlugin" and see if it reset...
                          Also theres no reason, for to undoRedo to reset those scripts. I dont know if you added it to "EditorPlugin" or the node the plugIN handels ?

                          not working in here:
                          https://github.com/godotengine/godot/issues/97502

                            jonSS Your arrogant attitude doesn't warrant this but here it is anyway 🙂

                            Does undo work? Of course it does. Shocking, I know. Editor inspector plugin has nothing to do with undo, did I already say that?

                            I suggest deleting that post on github unless you want to embarrass yourself. The only "issue" there is the lack of understanding of how this stuff is supposed to operate. I won't waste any more time trying to explain it to you just to be called an idiot in return. You don't listen. Here's the example with editor inspector plugin added on top. Learn from it. Or don't. Good luck 👍

                            undo-example.zip
                            4kB