Системы отмены и повторного действия давно стали незаменимым инструментом в программном обеспечении для творчества и работы с визуальными данными. Несмотря на свою кажущуюся простоту, создание действительно надежного и удобного механизма отмены/повтора в комплексном визуальном приложении представляет собой масштабную вызов. Особенно остро эта задача стоит, когда пользователь взаимодействует с приложением в нескольких контекстах, а эффекты действий распространяются по разным уровням интерфейса. Примером такой реализации стала система, созданная для Alkemion Studio — специализированного инструмента для визуального мозгового штурма и написания, ориентированного на настольные ролевые игры. В этом проекте возник ряд уникальных сложностей, которые послужили отправной точкой для создания инновационного решения.
В основе главного принципа лежит понятие контекстной осведомленности. В отличие от большинства текстовых редакторов, где Undo/Redo может оперировать одной последовательностью простых действий, приложения типа Alkemion Studio работают с несколькими интерфейсными слоями и различными объектами — от перемещения элементов на доске до правки текстовых блоков и метаданных. Отмена действия вне текущего видимого контекста может привести к путанице и даже потере данных. Концепция, на которой базируется система, заключается в запрете на отмену тех изменений, которые пользователь в данный момент не видит. Это обеспечивает прозрачность и предсказуемость работы интерфейса, исключая ситуации, когда при отмене ничего, похоже, не происходит, а в сторону удаленного контекста происходит скрытое изменение состояния.
Архитектурно система построена вокруг класса Action, представляющего отдельное действие пользователя, которое можно отменить и повторить. Каждое такое действие содержит методы undo и redo, чтобы обеспечить обратимое преобразование. Отдельно выделяются два подкласса — ActionSingle и ActionGroup. ActionSingle соответствует одному базовому действию, а ActionGroup объединяет несколько связанных действий в единую транзакцию, позволяя откатывать и повторять их комплексно, что особенно удобно в случаях, когда одно действие запускает цепочку изменений. Такой подход исключает глобальное хранение состояния приложения, вместо этого фиксируя только малые специфичные данные, необходимые для возврата состояния — будь то позиция токена или содержимое текстового блока.
Это улучшает масштабируемость и удобство отладки. Управление созданными объектами Action осуществляется через синглтон ActionStore, выступающий централизованным хранилищем и менеджером состояния. Он распределяет и хранит actions в двух основных объемах — для выполненных и отмененных действий, организуя их по классам для упрощения поиска и обращения. Здесь же реализуется механизм слежения за контекстом — динамическим состоянием приложения, которое определяет, какие действия доступны для Undo/Redo в текущей ситуации. Таким образом, при работе пользователя в разных частях интерфейса система способна моментально выбирать релевантный стек действий.
Контекст чаще всего представлен строковым идентификатором и обновляется согласно положениям приложения. Для удобства и контроля используется конфигурационный файл, который описывает набор допустимых для отмены и повторения действий для каждого контекста. Использование стека позволяет гарантировать последовательность и корректный порядок отменяемых операций, базируясь на свойстве globalIndex, проставляемом при создании действия. Это обеспечивает выполнение Undo и Redo в естественном для пользователя порядке — последний выполненный шаг отменяется первым, и наоборот. Управление индексами в многоконтекстной среде — одна из ключевых сложностей.
Если просто фиксировать действие с присвоением возрастающего индекса, переключение контекстов может привести к неконсистентности — когда действие, отмененное в одном контексте, оказывается в неправильном месте при повторном вызове. Для решения этого применяется специальный алгоритм смещения индексов, перераспределяющий позиции действий при создании нового действия, а также при Undo и Redo, с учётом состояния действия в каждом контексте. Это гарантирует интуитивно понятное поведение системы, при котором действие становится первым на очереди отмены или повторения именно тогда, когда это ожидают пользователи. Помимо этого в архитектуру внедрён дополнительный концепт — контейнеры. Они служат для изоляции групп действий, например, внутри модальных окон или временных UI-состояний, где все изменения должны происходить замкнуто и могут быть либо отменены, либо сохранены как единый блок при выходе из контейнера.
Контейнеры похожи на аналогии с Docker, придавая системе ядро устойчивости и гибкости. В каждый момент времени активен только один контейнер, но возможна фонова кешировка других объектов с уникальными ID. Этот механизм обеспечивает атомарность и управляемость, позволяя контролировать приоритеты и разрешения для конкретных наборов действий внутри контейнеров и на глобальном уровне с помощью их «верховенства». Важной проблемой, которая требует внимания, остаются зависимости между действиями. Иногда операции зависят от результатов предыдущих шагов, и их отмена в неправильном порядке может привести к конфликтам и ошибкам.
В Alkemion Studio пока что такие ситуации организованы строго по правилам, чтобы не нарушать целостность. Однако в будущем планируется использовать граф зависимостей для полной гарантии правильности последовательности Undo/Redo. Такой граф позволит детально отслеживать связи между действиями и предотвращать некорректную отмену путем автоматического вычисления допустимых сценариев. Реализация системы Undo/Redo в сложном визуальном приложении требует глубокого понимания архитектуры, поведения пользователя и особенностей многоконтекстной среды. Интуитивно понятный, контекстно-зависимый механизм — залог положительного пользовательского опыта.
Он позволяет предотвращать потери данных, устраняет путаницу и добавляет уверенности в процессе работы с локальными и глобальными объектами интерфейса. Используемые подходы с Action-классами, управлением через централизованный стор и введением контейнеров демонстрируют современное видение проблемы и способствуют созданию масштабируемых систем с возможностью гибких улучшений в будущем. Таким образом, грамотная реализация Undo/Redo — не только техническая деталь, но важная составляющая взаимодействия пользователя с продуктивным интерфейсом и серьезным конкурентным преимуществом для продуктов с расширенным функционалом визуального редактирования.