VisualUIEditor項目講解之撤消反撤消詳解

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市踏志,隨后出現(xiàn)的幾起案子阀捅,更是在濱河造成了極大的恐慌,老刑警劉巖针余,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件饲鄙,死亡現(xiàn)場離奇詭異,居然都是意外死亡圆雁,警方通過查閱死者的電腦和手機忍级,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來摸柄,“玉大人颤练,你說我怎么就攤上這事∏海” “怎么了?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵患雇,是天一觀的道長跃脊。 經(jīng)常有香客問我,道長苛吱,這世上最難降的妖魔是什么酪术? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮翠储,結(jié)果婚禮上绘雁,老公的妹妹穿的比我還像新娘。我一直安慰自己援所,他們只是感情好庐舟,可當我...
    茶點故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著住拭,像睡著了一般挪略。 火紅的嫁衣襯著肌膚如雪历帚。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天杠娱,我揣著相機與錄音挽牢,去河邊找鬼。 笑死摊求,一個胖子當著我的面吹牛禽拔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播室叉,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼睹栖,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了太惠?” 一聲冷哼從身側(cè)響起磨淌,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎凿渊,沒想到半個月后梁只,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡埃脏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年搪锣,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片彩掐。...
    茶點故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡构舟,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出堵幽,到底是詐尸還是另有隱情狗超,我是刑警寧澤,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布朴下,位于F島的核電站努咐,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏殴胧。R本人自食惡果不足惜渗稍,卻給世界環(huán)境...
    茶點故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望团滥。 院中可真熱鬧竿屹,春花似錦、人聲如沸灸姊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽厨钻。三九已至扼雏,卻和暖如春坚嗜,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背诗充。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工苍蔬, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蝴蜓。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓碟绑,卻偏偏與公主長得像,于是被迫代替她去往敵國和親茎匠。 傳聞我的和親對象是個殘疾皇子格仲,可洞房花燭夜當晚...
    茶點故事閱讀 44,629評論 2 354

推薦閱讀更多精彩內(nèi)容