题记:
如果需要一个
撤销、重做
的机制可以看看这篇文章。
记录一下BabylonJS 编辑器项目中的Undo-Redo撤销重做机制。
BabylonJS Editor源码地址:>github.com/BabylonJS/E…
一、如何实现
定义一个
指令类
,包含:撤销、重置、回调等函数体。定义一个工具栏,声明指令栈stack,在合适的地方塞入指令、弹出指令即可。
-
IUndoRedoAction
:UndoRedo的行为指令- common: 当新增指令、撤销指令、重置指令时执行的一些行为函数
- undo:撤销行为函数
- redo:重做行为函数
/**
* Defines the type used to define the return type of undo/redo actions.
*/
export type UndoRedoReturnType<T> = (T | void) | (Promise<T> | Promise<void>);
export interface IUndoRedoAction {
/**
* Defines the description of the undo/redo-able action that has been performed.
*/
description?: string;
/**
* Defines the callback called on an undo or redo action has been performed. This
* is typically used to perform an action in both cases (undo and redo).
*/
common?: (step: "push" | "redo" | "undo") => UndoRedoReturnType<unknown>;
/**
* Defines the callback called on an action should be undone.
*/
undo: () => UndoRedoReturnType<unknown>;
/**
* Defines the callback called on an action should be redone.
* Calling undoRedo.push(...) will automatically call this callback.
*/
redo: () => UndoRedoReturnType<unknown>;
}
定义操作指令的工具类 维护一个 stack,将指令行为进行管理.
export class UndoRedo {
public _position: number = -1;
private _stack: IUndoRedoAction[] = [];
/**
* Gets the reference to the current stack of actions.
*/
public get stack(): ReadonlyArray<IUndoRedoAction> {
return this._stack;
}
/**
* Pushes the given element to the current undo/redo stack. If the current action index
* is inferior to the stack size then the stack will be broken.
* @param element defines the reference to the element to push in the undo/redo stack.
*/
public push<T>(element: IUndoRedoAction): UndoRedoReturnType<T> {
// Check index
if (this._position < this._stack.length - 1) {
this._stack.splice(this._position + 1);
}
// Push element and call the redo function
this._stack.push(element);
return this._redo("push");
}
/**
* Undoes the action located at the current index of the stack.
* If the action is asynchronous, its promise is returned.
*/
public undo<T>(): UndoRedoReturnType<T> {
return this._undo();
}
/**
* Redoes the current action located at the current index of the stack.
* If the action is asynchronous, its promise is returned.
*/
public redo<T>(): UndoRedoReturnType<T> {
return this._redo("redo");
}
/**
* Called on an undo action should be performed.
*/
private _undo<T>(): UndoRedoReturnType<T> {
if (this._position < 0) {
return shell.beep();
}
const element = this._stack[this._position];
const possiblePromise = element.undo();
if (possiblePromise instanceof Promise) {
possiblePromise.then(() => {
element.common?.("undo");
});
} else {
element.common?.("undo");
}
this._position--;
return possiblePromise as UndoRedoReturnType<T>;
}
/**
* Called on a redo action should be performed.
*/
private _redo<T>(step: "push" | "redo"): UndoRedoReturnType<T> {
if (this._position >= this._stack.length - 1) {
return shell.beep();
}
this._position++;
const element = this._stack[this._position];
const possiblePromise = element.redo();
if (possiblePromise instanceof Promise) {
possiblePromise.then(() => {
element.common?.(step);
});
} else {
element.common?.(step);
}
return possiblePromise as UndoRedoReturnType<T>;
}
/**
* Clears the current undo/redo stack.
*/
public clear(): void {
this._stack = [];
this._position = -1;
}
}
二、使用
我们需要检测的对象发生改变时,加入一个撤销重做行为到指令栈
undoRedo.push({
description: `Changed object transform "${attachedMesh.name}" from "${endValue.toString()}" to "${initialValue.toString()}"`,
common: () => console.log("操作指令")),
redo: () => console.log("执行了重置操作"),
undo: () => console.log("执行了撤销操作"),
});
然后在工具栏加一个撤销重做的按钮 或者检测crtl+z ,和ctrl+shift+z 绑定执行undo 和redo的方法。
...
switch(action){
// Edit
case Action.Undo: undoRedo.undo(); break;
case Action.Redo: undoRedo.redo(); break;
}
...
哈哈哈哈 一个简单的undo、redo机制。不过适用于很多场景了。在这个基础上我们还可以为指令进行额外扩展。
如何自动化检测添加行为呢?
方案可参考使用:Proxy、immutable.js
下期预告: BabylonJS-Editor编辑器的设置面板自动化配置原理。
4/300