/
docNemo
/
clothes-graph
Обзор
Документация
Войти
/
docNemo
/
clothes-graph
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
main
src/ClothesGraph.Model/Editing/DraftActions.cs
136 строк
5 KB
docNemo
Запрос на слияние 'interface-to-spec' (
#1
) из interface-to-spec в main
09 авг 2026, 20:28
Верифицирован
09 авг 2026, 20:28
1bbc436
Код
Авторство
О чём код?
using System; using System.Collections.Generic; using System.Linq; using ClothesGraph.Model.Drafting; namespace ClothesGraph.Model.Editing; /// <summary>Добавление построения.</summary> public sealed class AddNodeAction(DraftGraph graph, DraftNode node) : IUndoableAction { public string Name => $"Добавление «{node.Name}»"; public void Apply() { if (!graph.Contains(node.Id)) graph.Add(node); } public void Revert() => graph.Remove(node.Id, force: true); } /// <summary> /// Удаление построения вместе со всем, что от него зависело. /// </summary> /// <remarks> /// Для отмены запоминаются и зависимые построения: восстановить нужно всю /// удалённую ветвь, иначе отмена вернёт чертёж не в то состояние, из которого /// пользователь его увёл. /// </remarks> public sealed class RemoveNodeAction : IUndoableAction { private readonly DraftGraph _graph; private readonly NodeId _id; private readonly List<DraftNode> _removed = []; public RemoveNodeAction(DraftGraph graph, NodeId id) { _graph = graph; _id = id; Name = $"Удаление «{graph.Get(id).Name}»"; } public string Name { get; } public void Apply() { _removed.Clear(); // Порядок важен: при восстановлении зависимые должны добавляться // после тех, на кого ссылаются. var order = _graph.TopologicalOrder(); var doomed = new HashSet<NodeId>( _graph.GetDependents(_id).Select(node => node.Id)) { _id }; foreach (var node in order.Where(node => doomed.Contains(node.Id))) _removed.Add(node.Clone()); _graph.Remove(_id, force: true); } public void Revert() { foreach (var node in _removed) _graph.Add(node); } } /// <summary>Изменение построения: формул, ссылок, режима.</summary> public sealed class UpdateNodeAction : IUndoableAction { private readonly DraftGraph _graph; private readonly NodeId _id; private readonly Action<DraftNode> _change; private readonly DraftNode _before; private DraftNode? _after; public UpdateNodeAction(DraftGraph graph, NodeId id, string name, Action<DraftNode> change) { _graph = graph; _id = id; _change = change; _before = graph.Snapshot(id); Name = name; } public string Name { get; } public void Apply() { if (_after is not null) { _graph.Restore(_after); return; } _graph.Update(_id, _change); _after = _graph.Snapshot(_id); } public void Revert() => _graph.Restore(_before.Clone()); } /// <summary> /// Перемещение построения указателем. /// </summary> /// <remarks> /// Отдельное действие от <see cref="UpdateNodeAction"/> потому, что снимок /// «до» берётся в начале перетаскивания, а в журнал действие попадает в конце, /// когда построение уже сдвинуто. Одно действие на всё перетаскивание, а не на /// каждое движение указателя: иначе отмена возвращала бы точку по шагу за раз, /// а журнал забивался бы сотнями записей за один жест. /// </remarks> public sealed class MoveNodeAction(DraftGraph graph, DraftNode before, DraftNode after) : IUndoableAction { public string Name => $"Перемещение «{after.Name}»"; // Снимки клонируются при каждом применении: иначе повторная отмена // работала бы с тем же объектом, который уже лежит в графе. public void Apply() => graph.Restore(after.Clone()); public void Revert() => graph.Restore(before.Clone()); } /// <summary>Переименование построения.</summary> public sealed class RenameNodeAction(DraftGraph graph, NodeId id, string newName) : IUndoableAction { private readonly string _oldName = graph.Get(id).Name; public string Name => $"Переименование в «{newName}»"; public void Apply() => graph.Rename(id, newName); public void Revert() => graph.Rename(id, _oldName); }