lil-gui
Undo / Redo
This example makes an undo system by combining gui.onFinishChange with load() and save().
This example makes an undo system by combining gui.onFinishChange with load() and save().
import GUI from '../../dist/lil-gui.esm.js';
const gui = new GUI( { captureKeys: false } );
const params = {
boolean: true,
string: 'lil-gui',
number: 0,
color: '#aa00ff',
};
gui.add( params, 'boolean' );
gui.add( params, 'string' );
gui.add( params, 'number' );
gui.addColor( params, 'color' );
const undoFolder = gui.addFolder();
const undoBtn = undoFolder.add( { undo }, 'undo' ).name( 'Undo ⌘Z' );
const redoBtn = undoFolder.add( { redo }, 'redo' ).name( 'Redo ⌘⇧Z' );
// Undo / Redo
const undoStack = [];
const redoStack = [];
function storeUndo() {
undoStack.push( gui.save() );
redoStack.length = 0;
updateUndoCount();
}
// There will always be at least 1 undo state in the stack: the initial state.
storeUndo();
// Store an undo step every time a change is finished. We use `loading` to
// avoid an infinite loop when loading an undo / redo state.
let loading = false;
gui.onFinishChange( () => {
if ( !loading ) storeUndo();
} );
function undo() {
if ( undoStack.length > 1 ) {
// Move the current state from the undo stack to the redo stack.
redoStack.push( undoStack.pop() );
// Load the state on top of the undo stack.
loading = true;
gui.load( undoStack[ undoStack.length - 1 ] );
loading = false;
}
updateUndoCount();
}
function redo() {
if ( redoStack.length > 0 ) {
// Remove the top-most state from the redo stack and put it back
// on the undo stack.
const state = redoStack.pop();
undoStack.push( state );
// Load the state on top of the undo stack.
loading = true;
gui.load( state );
loading = false;
}
updateUndoCount();
}
function updateUndoCount() {
// The undo stack is "empty" when there's one item left: the initial state.
const numUndos = undoStack.length - 1;
const numRedos = redoStack.length;
// Use a folder title as a readout.
undoFolder.title( `Undo (${numUndos}) • Redo (${numRedos})` );
undoBtn.disable( numUndos < 1 );
redoBtn.disable( numRedos < 1 );
}
// Unless `captureKeys` is false, this won't be called while a GUI input has focus.
window.addEventListener( 'keydown', e => {
if ( e.key === 'z' && ( e.metaKey || e.ctrlKey ) ) {
// Prevent native keystroke-based undo
e.preventDefault();
// Blur element if it's an <input>
if ( e.target instanceof HTMLInputElement ) {
e.target.blur();
}
if ( e.shiftKey ) redo();
else undo();
}
} );