3 Undo and Redo
dxtr edited this page 2026-10-20 10:09:08 +02:00

Undo and Redo

pyssel/editor keeps a history of the edits made to each scene, which undo undoes and redo makes again, as Godot's editor keeps one for each scene. The editor's docks and inspector change scenes only through a history, and so may your code:

(asdf:load-system :pyssel/editor)

(let* ((level (make-instance 'pyssel/scene-2d:node-2d :name "level"))
       (player (pyssel/scene:add-child level (make-instance 'pyssel/scene-2d:node-2d
                                                            :name "player")))
       (history (pyssel/editor:make-history :name "level.scene")))
  (pyssel/editor:rename-node history player "hero")
  (pyssel/editor:set-property history player :position (cl-gmath:make-dvec2 :x 40 :y 100))
  (list (pyssel/editor:undo history)          ; => "Set position"
        (pyssel/editor:undo history)          ; => "Rename Node"
        (pyssel/scene:node-name player)       ; => "player"
        (pyssel/editor:history-redo-name history)))  ; => "Rename Node"
  • Edits: set-property, of a property named by its initarg, as :position, or by its name; add-node, delete-node, move-node, which reorders or reparents, and rename-node; connect-signal, which connects to persist, and disconnect-signal; add-node-to-group and remove-node-from-group; set-node-meta and remove-node-meta, for a node's metadata, named by :edit-name, as "Lock Selected". Each makes its change an edit, named for a menu, as history-undo-name and history-redo-name give them: "Undo Rename Node". perform-change makes any other change an edit, given as a function that makes it and one that undoes it.
  • Several changes as one: with-edit makes the changes of its body one edit, undone together, and undoes them should the body fail. Its :merge, a key standing for a gesture made of steps, as a drag in the 2D viewport, has each step join the edit the gesture's first step made, while nothing else changes the history, as Godot's MERGE_ENDS: one undo takes the drag back whole, and a drag let go where it began leaves no edit.
  • Merging: settings of one property in a row merge into one edit, so a slider's drag is one step; :merge nil keeps them apart.
  • Saving: mark-saved notes that the scene was saved, and history-saved-p tells whether it is as it was then, after undoing what was done since or redoing what was undone; mark-unsaved notes that it differs from its file in a way no edit undoes, as when a resource it holds was saved to a file of its own; history-version moves on at every change, for a user interface that shows the history, and history-structure-version only at an edit that does more than set properties, for one that shows the scene's nodes but not their properties, as the Scene dock, which a drag that sets a position each frame then leaves be.
  • Deleted nodes stay with their edits, to be put back, and are freed when the history lets the edit go: when undone edits give way to a new one, when it keeps more than make-history's :limit, and at clear-history.
  • Changes made otherwise, as by (setf node-name) at the REPL, go around the history, which cannot undo them. An undo that finds its scene changed so signals an error and leaves the scene as it was.
  • Separate histories: each scene has its own, and the project its own for its settings and the resources opened from the File System dock, so undoing in one never changes another. Histories made with one :clock, which make-clock makes, stamp their edits from it: newest-undo finds the one among them whose edit came last, and earliest-redo the one whose undone edit came first, as the editor's Control+Z undoes the newest edit of the scene shown and the project. Text fields keep their own undo, and a focused one takes Control+Z before the editor's shortcut does.