This page explains how you can make iink SDK operations part of the undo/redo stack of your application.

Use case

If your application is based on an iink SDK editor, undo/redo management is available out of the box, as shown in the editing part of the guide.

Chance is, however, that your application is more complex, and that you may need to integrate iink SDK operations into your own undo/redo stack.

Conceptual undo/redo stack

The application undo/redo stack can be seen as a vector of states, each but the first resulting from a specific operation.

In the following example, the current state is and results from applying operation op5 to the previous state :

1 2 3 4 5 op1 op2 op3 op4 op5 current

Calling undo reverts the effect of the last operation. It is equivalent to moving from the current state in the stack to the state on its left.

In the example, if the current state is , two successive undos lead to state :

1 2 3 4 5 op1 op2 op3 op4 op5 current

You can notice that states and are still part of the stack. This is because no new state was created and it shall thus be possible to redo.

Calling redo applies the next operation on the stack. It is equivalent to moving from the current state in the stack to the state on its right.

In the example, if the current state is , a redo will move back to state :

1 2 3 4 5 op1 op2 op3 op4 op5 current

If a new operation op6 is performed from state , the resulting state becomes the new current state, and state is gone from the stack:

1 2 3 4 6 op1 op2 op3 op4 op6 current

Including iink SDK operations

It may be required for the application integrating iink SDK to mix its own operations with those managed by iink SDK.

For instance, the application may add an image to a layer below an iink SDK text block, add a stroke to the text block, and finally select and delete the image. The expectation at this point is that if the user presses two times the undo button, the first press brings the image back and the second removes the stroke that was sent to iink SDK.

Situation before the user presses undo:

1 2 3 add image iink op deleteimage current

Situation after the first undo:

1 2 3 add image iink op deleteimage current

Situation after the second undo:

1 2 3 add image iink op deleteimage current

The application needed to include and manipulate the effects of iink SDK operations.

iink-resulting states are shown with a blue background in the different examples.

Managing iink SDK operations

The challenge is that iink SDK maintains its own internal undo/redo stack and that the host application does not have access to the operations it contains. It is however possible to query iink SDK to get information about the state of its internal stack to implement the desired behavior.

From now on, you should consider that the application does not know exactly what the iink operations actually do (it actually does not matter). Let’s show how it can still properly manage its undo/redo stack.

Let’s go back to the example. How to obtain this stack?

1 2 3 add image iink op deleteimage current

The changed event provides the following details about the undo stack :

You can know when iink SDK adds new “undoable” operations by watching the increments of details.undoStackIndex. You can compare a previously stored value with the one returned after a changed event.

Let’s see how it applies to the example. Let’s consider that you have set up an iink SDK editor with type TEXT. First, you add your image. As it is done outside of iink SDK, no changed event is trigger by the editor.

The application and iink SDK editor stacks respectively look as follows:

1 add image current current Application: iink Editor: stackIndex = 0

You now send a stroke to the iink SDK editor.

You may get one or more changed events and test for details.undoStackIndex increments. If the stroke is not discarded as a gesture (it shouldn’t!), details.undoStackIndex is incremented:

1 add image current current 1 iink op Application: iink Editor: stackIndex = 1

As you know that iink SDK internal stack added a new state. You can add a new state to the application stack corresponding to the effect of an opaque iink operation:

1 2 add image iink: “1” current current 1 iink op Application: iink Editor: stackIndex = 1

The value of details.undoStackIndex obtained after the iink operation is stored into the application stack. It is useful to be able to see whether the value you get from changed events has changed and to react accordingly.

You can now select and delete your image outside of iink SDK. Like for the first operation, changed event is not triggered :

1 2 3 add image iink: “1” deleteimage current current 1 iink op Application: iink Editor: stackIndex = 1

Now, let’s undo. As the current application state results from an application-level operation, it is up to the application to restore the image:

1 2 3 add image iink: “1” deleteimage current current 1 iink op Application: iink Editor: stackIndex = 1

Let’s undo again. The current state results from an internal iink SDK operation. One should thus call undo() on the iink SDK editor when moving the cursor on the application stack:

1 2 3 add image iink: “1” deleteimage current current 1 iink op Application: iink Editor: stackIndex = 0

Now, if you wanted to redo, you would first call redo() on the iink SDK editor and move the cursor to the right, as the operation to redo was an iink operation (details.undoStackIndex is incremented to 1 but as it comes from a redo operation, you should not consider it as a new operation that would prevent a next redo by clearing the stack). Redo again and this time manage the image deletion on the application side.

The iink SDK undo stack is partially purged from time to time to control memory consumption. As a result, it may sometimes not be able to revert all the states that were are known by the application-level stack. To know if an undo is actually possible, you should check the value of details.possibleUndoCount, that tells you how many steps can be undone by iink SDK from its current internal state.