Skip to content

Undo redo design guidelines

Sven Nilsen edited this page Feb 16, 2017 · 5 revisions

Coding undo/redo logic is not always as straight forward as it seems at first.

When recovering a view, you should not store data that depends on the size of the window or UI layout. Instead, you should call a method to display the selected item e.g. control.navigate_to_selection().

Examples of views:

  • Camera view
  • Selected animation frame(s)
  • Selected layer(s)
  • Selected object
  • Selected key frame(s)
  • Selected line(s) when editing text

These 3 points are required for good user experience:

  1. User must be able to see changes when using undo/redo by changing application view.
  2. Changing application view only should not change history, as long the user does not make any changes.
  3. When redoing to the last performed action, application view should be restored to the last one seen before undoing.

Here is an illustration of what it is like:

       / letter represent an action
      /       / last application view (stored when browsing history)
     v       v
|A|B|C|D|E|F()  (|) <------ current application view
    ^
     \ `|` represents the application view between actions

When at the top of history, any changes to view should not be pushed to history, but mutate the current application view.

                  / make changes to view by mutating current application view
                 v
|A|B|C|D|E|F()  (|)
            ^
             \ notice there is no view in history after the last action
           / when undoing, jump to the last view before the last action
          v
|A|B|C|D|E|F(|)        (.)
             ^          v
              \         |
               \ store the last application view so it can be recovered
               when redoing the last action, recover the last application view
              /       /
             ^       v
|A|B|C|D|E|F(.)     (|)
      / mutate current application view when browsing from a point in history
     /               |
    ^                v
|A|B|C|D|E|F(|)     (|)
      / when making changes, the view before making a change is stored in history
     /          / the view after making the change becomes current view
    v          v
|A|B|G()      (|)
     ^
      \ history is truncated and the new action is stored

For actions that are not instantaneous, the view before doing the action must be remembered until it can be pushed to history.

One way to solve this, is to push action labels and treat actions between them as sub-actions.

   / action label
  /  / sub-actions
 v  v
|a|AAAb|BBBc|CC()      (|)
  ^
   \ view before sub-actions

Using sub-actions has the advantage that you can compose them into larger actions. For example, when doing a large number of steps by a scripting tool or a helper UI.

Clone this wiki locally