1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/tickbh-VisualUIEditor

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
redoundo.md 9.9 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
gitlife-traslator Отправлено 26.11.2024 17:41 b467939

Визуальный редактор пользовательского интерфейса: подробное объяснение отмены и повтора действий

Отмена и повтор действий в визуальном редакторе пользовательского интерфейса — это незаменимые функции, которые помогают избежать ошибок при редактировании и делают использование редактора более удобным.

Использование в проекте

Если вы случайно переместили элемент или установили неправильное свойство в своём проекте, вы можете использовать сочетание клавиш Ctrl+Z для отмены последнего действия. Если после отмены вы хотите быстро вернуться к предыдущему состоянию, используйте сочетание клавиш Ctrl+R для повтора отменённого действия.

Исходный код

Путь к исходному коду

Исходный код проекта можно найти по ссылке renderUndo.

Детали исходного кода

Каждый экземпляр сцены создаёт объект UndoObj, который представляет собой оболочку над списком UndoList. Разница между ними заключается в том, что обычно редактор реагирует на изменения узлов при отмене и повторе действий.

"ui:scene_change"(event, message) {
    let runScene = this.$.scene.getRunScene();
    if(!runScene._undo)
        runScene._undo =  new UndoObj();
}

Список UndoList хранит историю изменений сцены. Его конструктор выглядит следующим образом:

class UndoList extends EventEmitter {
  constructor (type) {
    super()
    //истина, если изменение вызывает событие
    this._silent = false
    //разделяется на локальный и глобальный типы, локальное событие изменения только уведомляет локально, глобальное обычно уведомляет весь редактор
    this._type = type

    //текущая группа команд
    this._curGroup = new CommandGroup()
    //список групп операций
    this._groups = []
    //записывает текущую позицию информации
    this._position = -1
    //позиция сохранения при последнем сохранении
    this._savePosition = -1
  }
}

Когда происходит новое действие, например, изменение положения узла, вызывается функция:

add (cmd) {
    this._clearRedo()
    if (this._curGroup.isCanCombine(cmd)) {
        this._curGroup.combineCommand(cmd)
    } else {
        this.commit()
        this._curGroup.add(cmd)
    }
    this._changed('add-command')
}

Поскольку вся система отмены и повтора является линейной, при появлении нового действия предыдущая операция повтора становится бессмысленной. В этом случае сначала очищается список повторов, то есть список команд после текущей позиции. Затем мы проверяем, можно ли объединить команду с текущей группой. Если возможно, мы пытаемся объединить команды.

  //Функция CommandGroup
  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
  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 мы просматриваем все команды и определяем, можно ли их объединить или последняя команда была создана недавно. Мы считаем, что команды можно объединить, если они соответствуют определённым условиям. Если объединение возможно, команды объединяются и значения свойств обновляются.

Почему такая конструкция?

Потому что вся система отмены предполагает, что она ничего не знает о других системах, кроме ограниченного интерфейса Add. Другие системы не предоставляют никакой другой информации об интерфейсе. Ниже приведены два примера:

  • При редактировании в редакторе перемещение узла вызывает событие mousemove каждые 350 мс. В этот момент свойства узла часто меняются, но мы рассматриваем это как одно действие и ожидаем, что после отмены перемещения узел должен вернуться в исходное состояние, а не в промежуточное. В таких случаях мы объединяем операции с одинаковыми свойствами в течение определённого времени.
  • Когда мы перемещаем или изменяем свойства нескольких узлов одновременно в редакторе, ожидается, что отмена вернёт все узлы к их состоянию до модификации. Поэтому мы объединяем все операции в одну группу.

Если объединение невозможно, создаётся новая группа команд, и информация о позиции обновляется.

Отмена (Undo)

  undo () {
    // проверяем, можем ли мы отменить текущую группу
    if (this._curGroup.canCommit()) {
      this._curGroup.undo()
      this._changed('undo-cache')
      this._groups.push(this._curGroup)
      this._curGroup = new CommandGroup()
      return true
    }

    // проверка возможности отмены
    if (this._position < 0) {
      return false
    }

    пусть group = this._groups[this._position]
    group.undo()
    this._position--
    this._changed('undo')
    вернуть истину
  }

При отмене возможны следующие ситуации:

  • Текущая группа может быть зафиксирована, то есть текущая группа содержит некоторую информацию об операции, и мы ещё не зафиксировали её. В этом случае мы напрямую выполняем операцию отмены для текущей группы и обновляем информацию о списке.
  • Нет информации для отмены, то есть позиция меньше 0.
  • Можно отменить, получить текущую группу позиций для отмены и обновить информацию о позиции.
  //Команда отмены
  undo () {
    пусть node = cocosGetItemByUUID(this.info.scene, this.info.uuid)
    если (this.info.op == 'prop') {
      если (!node) {
        вернуть ложь
      }
      если (this.info.doPropChange) {
        this.info.doPropChange(узел, this.info.prop, this.info.oldValue)
      } иначе {
        NodePropChange(узел, this.info.prop, this.info.oldValue)
      }
      вернуть истину
    }
    console.warn('Пожалуйста, реализуйте функцию отмены в вашей команде')
  }

Каждая команда списка сначала пытается получить узел, а затем устанавливает новое и старое значения в соответствии со свойствами узла.

Повтор (Redo)

  redo () {
    // проверить возможность повтора
    если (это._позиция >= это._группы.длина - 1) {
      вернуть ложь
    }

    это._позиция++
    пусть группа = это._групп[это._позиция]
    группа.redo()

    это._изменено('повторить')

Сначала проверяется, возможно ли повторить действие. Если да, позиция увеличивается, берётся соответствующая группа из списка групп и выполняется операция повтора.

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/tickbh-VisualUIEditor.git
git@api.gitlife.ru:oschina-mirror/tickbh-VisualUIEditor.git
oschina-mirror
tickbh-VisualUIEditor
tickbh-VisualUIEditor
master