Question

This question is about the architecture approach. How would someone go about creating the immutable state for a graphics editor that would eventually provide an "undo/redo" functionality, considering that the library for canvas manipulations already has its own state management.

I am not sure that I'd be doing the right thing at all. Maybe it's just stupid to take something what already has its own state management and try to abstract over it. Any opinion would be great! If anything, I'll create an example out of it once I'm done.


EaselJS (a canvas manipulation library) is pretty straightforward for use.

It stores its state in the object you can get from new createjs.Stage("canvasId"); and it's usually assigned to a variable called stage.

You create canvas objects with constructors such as let circle = new createjs.Shape();, which you can color or shape with methods like circle.graphics.beginFill("red").drawCircle(0, 0, 50);, then you add those to the stage with stage.addChild(circle);, and finally you have to manually call the stage.update();

The state and representation of those objects on canvas is directly mutable and the only way for changing positions of elements on canvas is to change their X and Y positions in the stage (state) object, and then call the stage.update();

For example, to create a drag'n'drop functionality, all you need is to attach a callback function to a custom event called "pressmove". That event exists on all stage objects. In the callback function you can get the current mouse position on canvas from a passed event reference - event.stageX and event.stageY, which you use to change the state with event.currentTarget.x = event.stageX and event.currentTarget.y = event.stageY, and of course, call the stage.update();. Just take a look at the code:

circle.on("pressmove", evt => { 
    evt.currentTarget.x = evt.stageX; 
    evt.currentTarget.y = evt.stageY; 
    stage.update();
})

Now, what I'm thinking is creating my own representation of the state in my immutable data store which would track the creation and removal of those objects. Basically I would mirror all the important data from the Stage object. To be even more precise, I'd mirror its property called children, which is an array that has all the objects rendered on the canvas and those objects can also have their children. The example is the container object, which is usually used to group text and image object and I'll definitely use those for tooltips over images, for functionalities like resize and rotate.

So I am thinking of storing every Stage change - the creation/removal of objects, start/end positions on drag, etc. Basically everything except the changes of element positions that occur during the events such as the dragging, resizing and rotating (I'd only store start and end positions), because it would make undo/redo complicated and would probably overload the store snapshots.

Is my thinking correct and would someone have any advices? Has anyone created or done anything similar to this?

Was it helpful?

Solution

This makes perfect sense, and is similar to the way undo/redo are typically implemented in a graphics editor. I also recommend to design your editor as a set of finite state machines, with super states capturing behavior common to sub-states. Try to limit the lifespan of sub states to the minimum; for example, while a drag operation occurs.

Licensed under: CC-BY-SA with attribution
scroll top