VisualUIEditor項目講解之撤消反撤消詳解
撤消反撤消在UI編輯器里面必備的功能渐尿,它可以幫助我們在編輯過程中堵漱,無需害怕誤操作饰迹,助您更好的使用編輯器
在項目中使用
當您在項目中錯誤的移動位置与纽,或者設(shè)置了錯誤的屬性,這時候您可以用CTRL+Z來撤消剛才您進行的修改
當您在項目中撤消了剛才的操作芥玉,又想快速的回到剛才的狀態(tài)蛇摸,這時候您可以使用CTRL+R反撤消功能,來進行反撤消
源碼講解
源碼路徑
項目的實現(xiàn)源碼灿巧,可參考renderUndo
源碼詳解
每個Scene實例化的時候都會創(chuàng)建一個UndoObj赶袄,UndoObj里面是一層UndoList的淺封裝,差別是在Undo和Redo的時候通常編輯器有節(jié)點發(fā)生變更
"ui:scene_change"(event, message) {
let runScene = this.$.scene.getRunScene();
if(!runScene._undo)
runScene._undo = new UndoObj();
}
UndoList維持著場景變更的記錄抠藕,UndoList的構(gòu)造函數(shù)如下
class UndoList extends EventEmitter {
constructor (type) {
super()
//是否變化時發(fā)送事件的變化
this._silent = false
//分為local和global類型, local類型事件變化只會通知本地, global則會通常整個編輯器
this._type = type
//當前的命令列表
this._curGroup = new CommandGroup()
//操作過程的命令列表
this._groups = []
//記錄當前的位置信息
this._position = -1
//上一次保存時的位置信息
this._savePosition = -1
}
}
當有新的操作到達時, 比如節(jié)點移動位置發(fā)生變更, 這時候則會調(diào)用函數(shù)
add (cmd) {
this._clearRedo()
if (this._curGroup.isCanCombine(cmd)) {
this._curGroup.combineCommand(cmd)
} else {
this.commit()
this._curGroup.add(cmd)
}
this._changed('add-command')
}
因為整個撤消反撤消操作是單線的, 所以當有新的操作, 之前保存的redo操作變成無意義, 這時候會先清除redo列表, 即當前位置后命令列表
這時候我們會判斷該命令是否能夠合并, 如果能合并, 我們會優(yōu)先嘗試合并
//CommandGroup的函數(shù)
isCanCombine (other) {
if (this._commands.length == 0) {
return true
}
for ( let i = 0; i < this._commands.length; ++i) {
if (this._commands[i].isCanCombine(other)) {
return true
}
}
if (this._time && Math.abs(this._time - other.info.time) < 1000) {
return true
}
return false
}
//Command的函數(shù)
isCanCombine (other) {
if (!this.info || !other.info) {
return false
}
if (this.info.op != other.info.op) {
return false
}
if (this.info.uuid != other.info.uuid) {
return false
}
if (this.info.op == 'prop' && (this.info.prop != other.info.prop)) {
return false
}
if (Math.abs(this.info.time - other.info.time) >= 1000) {
return false
}
return true
}
在CommandGroup中, 我們會遍歷所有的Command饿肺,看能否進行合并或者最后一條Command的時間距離現(xiàn)在的時間太短,我們則認為能夠合并
在Command中盾似,同一種操作類型敬辣,同一個節(jié)點,同一個屬性操作,時間在一定的時間內(nèi)方可認為能合并
如果判斷能合并溉跃,則進行合并并更新屬性值
為什么如此設(shè)計汰聋?
因為整個撤消系統(tǒng)中,都是假定對其它系統(tǒng)一無所知的喊积,而其它系統(tǒng)除了有限的Add接口烹困,并沒有暴露其它的接口信息
下面列舉兩種情景
- 當在編輯器中,拖動某個節(jié)點移動的時候乾吻,每隔350ms會觸發(fā)一次mousemove事件髓梅,這時候節(jié)點屬性會頻繁更改,而我們又認為這是一次操作绎签,則我們應當在移動完成之后按一次撤消應該回到之前的狀態(tài)枯饿,而不應該是移動過程中的任何狀態(tài),這時候用到同屬性操作诡必,在一定時間內(nèi)合并操作
- 當在編輯器中奢方,一次拖動或者更改多個節(jié)點的屬性,這時候預期的撤消應該為這次修改的所有屬性同時回到變更之前的狀態(tài)爸舒,而不應該是一個個節(jié)點的屬性變更蟋字,所以這時候會合并所有的操作,當成一個組別
當判斷不能合并的時候扭勉,則會新起一條CommandGroup來記錄新的Command鹊奖,并更新位置信息
撤消Undo
undo () {
// check if we have un-commit group
if (this._curGroup.canCommit()) {
this._curGroup.undo()
this._changed('undo-cache')
this._groups.push(this._curGroup)
this._curGroup = new CommandGroup()
return true
}
// check if can undo
if (this._position < 0) {
return false
}
let group = this._groups[this._position]
group.undo()
this._position--
this._changed('undo')
return true
}
撤消時,存在以下三種情況
- 當前的Group可提交涂炎,即當前Group記錄著一些操作信息忠聚,并且我們沒有提交操作,這時直接對當前的Group執(zhí)行Undo唱捣,并更新列表信息
- 無可撤消的內(nèi)容两蟀,即位置信息小于0
- 可撤消,獲取當前位置的Group進行撤消震缭,并更新位置信息
//Command undo
undo () {
let node = cocosGetItemByUUID(this.info.scene, this.info.uuid)
if (this.info.op == 'prop') {
if (!node) {
return false
}
if (this.info.doPropChange) {
this.info.doPropChange(node, this.info.prop, this.info.oldValue)
} else {
NodePropChange(node, this.info.prop, this.info.oldValue)
}
return true
}
console.warn('Please implement undo function in your command')
}
每個命令列表赂毯,首先會先嘗試獲取節(jié)點,然后根據(jù)節(jié)點的屬性變更蛀序,進行新舊值的設(shè)置
反撤消Redo
redo () {
// check if can redo
if (this._position >= this._groups.length - 1) {
return false
}
this._position++
let group = this._groups[this._position]
group.redo()
this._changed('redo')
return true
}
存在以下兩種情況
- 當前位置處理列表的最后一位欢瞪,即無可反撤消的內(nèi)容,此時不執(zhí)行任何操作
- 獲取該反撤消的Group徐裸,對其執(zhí)行redo操作
//Command redo
redo () {
let node = cocosGetItemByUUID(this.info.scene, this.info.uuid)
if (this.info.op == 'prop') {
if (!node) {
return false
}
if (this.info.doPropChange) {
this.info.doPropChange(node, this.info.prop, this.info.newValue)
} else {
NodePropChange(node, this.info.prop, this.info.newValue)
}
return true
}
console.warn('Please implement redo function in your command')
}
每個命令列表遣鼓,首先會先嘗試獲取節(jié)點,然后根據(jù)節(jié)點的屬性變更重贺,進行新舊值的設(shè)置
在編輯器中添加Command
function addNodeCommand (node, prop, oldValue, newValue, doPropChange) {
let scene = getRootNode(node)
if (!scene._undo) {
return
}
tryAddCommand(scene._undo, newPropCommandChange(scene, node.uuid, prop, oldValue, newValue, doPropChange))
}
當屬性發(fā)生變更的時候骑祟,我們會調(diào)用addNodeCommand值進行相應的設(shè)置回懦,來添加撤消反撤消功能的支持,以下的參數(shù)說明
- node即發(fā)生變化的節(jié)點
- prop即發(fā)生變化的屬性值次企,值為x怯晕,width這種
- oldValue即當前node節(jié)點的值
- newValue為node屬性將要變更的值
- doPropChange如果沒有傳該函數(shù),則會調(diào)用NodePropChange進行修改缸棵,如果伴隨屬性變更舟茶,需調(diào)用相關(guān)函數(shù),則可以傳自定義函數(shù)堵第,原型如下
function(node, prop, newValue) {}
其它未列出的信息吧凉,可參考源碼的實現(xiàn)細節(jié)
其它信息
VisualUIEditor開發(fā)QQ群歡迎您的加入: 453224679