Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question Is there undo/redo support?

Discussion in 'UI Toolkit' started by melos_han_tani, Oct 18, 2022.

  1. melos_han_tani

    melos_han_tani

    Joined:
    Jan 11, 2018
    Posts:
    77
    If not, is there anything in Unity that would help in making a custom undo/redo stack thing? (Or having my UITK editor window taking able to take 'focus' away from the other unity undo/redo stuff)

    Great tool by the way!
     
  2. SimonDufour

    SimonDufour

    Unity Technologies

    Joined:
    Jun 30, 2020
    Posts:
    515
    If you are using the unity editor, usually the undo redo stack is meant for undoing changes to the serialized object. I think you could use a dummy serialized object to hold the state of your focus and call

    Undo.RecordObject(focusManager, "Change focus to something else");
    whenever you change something.

    Just to be clear, you need to record the object before modifying it.
     
  3. melos_han_tani

    melos_han_tani

    Joined:
    Jan 11, 2018
    Posts:
    77
    Thanks! I figured out what I needed. What I didn't know was that to undo something it has to be a serialized or scriptable object. I've only ever used Undo code with scene-interaction editor tools which are of course all GameObjects.

    For anyone in the future reading this: here's a mini-tutorial on Undo with UI Toolkit

    In the UI Toolkit use case, I was originally maintaining editor data as just a C# object (public class MyData { }...), so I couldn't register changes to the Undo system.

    I just made my data class inherit from ScriptableObject (public class MyData : ScriptableObject), changed any "new MyData()" calls to "CreateInstance(typeof(MyData))".

    As well as added [System.Serializable] to other custom data types that MyData contains, so that undo operations work on them. (In my case, MyData contains lists of custom data types.)

    --

    From a few other threads I got a decent undo system working. The main "trick" to realize is that any UI Toolkit UI should be hooked up to something undoable - e.g. a ScriptableObject. It's a little goofy feeling but more or less works. Here's two use cases.

    1. Changing the value of a dropdown:

    Code (CSharp):
    1.  
    2. Dropdown.RegisterValueChangedCallback(evt => {
    3.     Undo.RecordObject(myData,"Change Dropdown Value");
    4.     myData.dropdownIndex = Dropdown.index;
    5. });
    6.  
    This is almost good enough. The state of myData properly gets undone, but the UI doesn't get updated to reflect this. I think you can use bindings and serialized objects(?) to bind this, but I had trouble figuring out how to change MyData to a SerializedObject, so I gave up and instead did this, which adds a callback to undo/redo functions.

    HandleUndo will go and update all of the UI values so that it's in line with the state of myData.

    Code (CSharp):
    1.  
    2.     private void OnEnable() {
    3.         Undo.undoRedoPerformed += HandleUndo;
    4.     }
    5.  
    6.     private void OnDisable() {
    7.         Undo.undoRedoPerformed -= HandleUndo;
    8.     }
    9.  
    10.     void HandleUndo() {
    11.         Dropdown.index = myData.dropdownIndex;
    12.     }
    13.  
    This works for most of what I need, although I'm still not sure what the best solution is for recording the editing of a text field in one go (rather than like every single time a character is typed).
     
    SimonDufour likes this.
  4. melos_han_tani

    melos_han_tani

    Joined:
    Jan 11, 2018
    Posts:
    77
    I found an OK solution for text fields. It feels like a hack but it works so.....

    My Text fields have a callback that updates a scriptable object's string value, when the text field is edited.

    (editingEC is a custom, serializable property of activePage, the ScriptableObject. command_string1 is just the TextField.)

    Code (CSharp):
    1.  
    2.         command_string1.RegisterValueChangedCallback(evt => {
    3.             editingEC.string1 = command_string1.value;
    4.  });
    5.  

    Then I register these callbacks, which run when you click and click away from the string field. It's a bit redundant and slightly weirder than how undo in text works in Unity's inspector, I think, but it more or less works. In the above post's HandleUndo() function, I can then refresh whatever I need based on the undone activePage's data.

    Code (CSharp):
    1.  
    2.         command_string1.RegisterCallback<FocusOutEvent>(evt => {
    3.             Undo.RegisterCompleteObjectUndo(activePage,"FocusOut "+label);
    4.         });
    5.         command_string1.RegisterCallback<FocusInEvent>(evt => {
    6.             Undo.RegisterCompleteObjectUndo(activePage,"FocusIn "+label);
    7.         });
     
    SimonDufour likes this.