using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Ink; using System.Windows.Threading; namespace InkCanvasForClass.IccInkCanvas { /// /// 指示时光机的一个Action的类型 /// public enum TimeMachineActionType { /// /// 用户绘制的墨迹被添加 /// UserInputStrokeAdded, /// /// 墨迹被添加,这可能是由软件添加的 /// StrokeAdded, /// /// 墨迹被移除 /// StrokeRemoved, /// /// 用户使用了面积擦进行擦除 /// UserGeometryErased, /// /// 用户使用了墨迹擦进行擦除 /// UserStrokeErased, /// /// 用户使用了墨迹擦进行擦除 /// UserAreaErased, Test } public class TimeMachineAction { /// /// Action的类型 /// public TimeMachineActionType ActionType { get; protected set ; } private bool _isReversed = false; public event EventHandler ActionIsReversedChanged; /// /// 指示该Action是否被逆操作了。 /// 比如现在如果这是一个StrokeAdded的Action,当执行撤销后,墨迹消失,此时IsReversed应该为True。 /// public bool IsReversed { get => _isReversed; set { if (value == _isReversed) return; _isReversed = value; ActionIsReversedChanged?.Invoke(this,null); } } } /// /// 墨迹操作的Action /// public class TimeMachineStrokeAction : TimeMachineAction { public event EventHandler AsyncPerformerLocked; public event EventHandler AsyncPerformerUnlocked; public bool IsLocked { get; protected set; } = false; public TimeMachineStrokeAction(TimeMachineActionType type, IccStroke stroke) { ActionType = (new TimeMachineActionType[] { TimeMachineActionType.UserInputStrokeAdded, TimeMachineActionType.StrokeAdded, TimeMachineActionType.StrokeRemoved, }).Contains(type) ? TimeMachineActionType.StrokeAdded : type; Stroke = stroke; } public TimeMachineStrokeAction(TimeMachineActionType type, IccStroke stroke, long timeStamp) { ActionType = (new TimeMachineActionType[] { TimeMachineActionType.UserInputStrokeAdded, TimeMachineActionType.StrokeAdded, TimeMachineActionType.StrokeRemoved, }).Contains(type) ? TimeMachineActionType.StrokeAdded : type; Stroke = stroke; TimeStamp = timeStamp; } /// /// 墨迹。如果为UserInputStrokeAdded或StrokeAdded则为添加的墨迹,否则,StrokeRemoved就是被移除的墨迹。 /// public IccStroke Stroke { get; private set; } /// /// 执行这个操作的时间戳,-1为没有时间。 /// public long TimeStamp { get; private set; } = -1; /// /// 应用该Action所导致的更改到画布上,需要指定Dispatcher,StrokeCollection /// public void Perform(Dispatcher dispatcher, StrokeCollection strokes) { IsLocked = true; AsyncPerformerLocked?.Invoke(this,null); var task = dispatcher.InvokeAsync(() => { if (IsReversed) { if (ActionType == TimeMachineActionType.StrokeAdded || ActionType == TimeMachineActionType.UserInputStrokeAdded) strokes.Remove(Stroke); else if (ActionType == TimeMachineActionType.StrokeRemoved) try { strokes.Add(Stroke); } catch { } } else { if (ActionType == TimeMachineActionType.StrokeAdded || ActionType == TimeMachineActionType.UserInputStrokeAdded) try { strokes.Add(Stroke); } catch { } else if (ActionType == TimeMachineActionType.StrokeRemoved) strokes.Remove(Stroke); } }); task.Completed += (_, __) => { IsLocked = false; AsyncPerformerUnlocked?.Invoke(this, null); }; } } /// /// only internal usage /// internal class TimeMachineTestAction : TimeMachineAction { private long timeStamp; public TimeMachineTestAction() { ActionType = TimeMachineActionType.Test; timeStamp = new DateTimeOffset(DateTime.Now).ToUnixTimeMilliseconds(); } public void Perform() { Trace.WriteLine($"helloworld! reversed:{IsReversed} timestamp: {timeStamp}"); } } /// /// 基于 icc 修改的 时光机 V2 版本,提供了更精简的设计 /// public class TimeMachineV2 { /// /// 当前的Index /// private int _currentIndex = 0; public int CurrentIndex => _currentIndex; private List _actions = new List(); public TimeMachineAction[] Actions => _actions.ToArray(); public int ActionsCount => _actions.Count; /// /// 在撤销重做状态发生改变时触发 /// public event EventHandler UndoRedoStateChanged; /// /// 执行action的perform的时候用于锁住时光机 /// private bool _asyncPerformerLocker = false; private void asyncPerformerLockedOrUnLocked(object sender, EventArgs e) { if (sender is TimeMachineStrokeAction) { _asyncPerformerLocker = (sender as TimeMachineStrokeAction).IsLocked; } } /// /// 丢弃指针Index后的所有Actions并销毁锁事件 /// private void DropActionsAfterCursorIndex() { var range = _actions.GetRange(_currentIndex, _actions.Count - _currentIndex); foreach (var a in range) { if (a is TimeMachineStrokeAction) { (a as TimeMachineStrokeAction).AsyncPerformerUnlocked -= asyncPerformerLockedOrUnLocked; (a as TimeMachineStrokeAction).AsyncPerformerLocked -= asyncPerformerLockedOrUnLocked; } } _actions.RemoveRange(_currentIndex, _actions.Count - _currentIndex); } public void PushStrokeAction(TimeMachineActionType type, IccStroke stroke, long timeStamp) { var action = new TimeMachineStrokeAction(type, stroke, timeStamp); _actions.RemoveRange(_currentIndex, _actions.Count - _currentIndex); _actions.Add(action); _currentIndex++; UndoRedoStateChanged?.Invoke(this,null); } /// /// only internal usage /// public void PushTestAction() { var action = new TimeMachineTestAction(); _actions.RemoveRange(_currentIndex, _actions.Count - _currentIndex); _actions.Add(action); _currentIndex++; UndoRedoStateChanged?.Invoke(this,null); } public void Undo() { Undo(false); } /// /// 撤销 /// public void Undo(bool doNotNotifyStateUpdate = false) { if (!(_currentIndex > 0)) return; if (_asyncPerformerLocker) return; var item = _actions[Math.Max(_currentIndex-1,0)]; if ((new TimeMachineActionType[] { TimeMachineActionType.Test, }).Contains(item.ActionType)) { (item as TimeMachineTestAction).IsReversed = true; (item as TimeMachineTestAction).Perform(); } var index = Math.Min(Math.Max(_currentIndex - 1, 0), _actions.Count); _currentIndex = index; if (!doNotNotifyStateUpdate) UndoRedoStateChanged?.Invoke(this,null); } /// /// 多步撤销 /// /// 步长 public void Undo(bool doNotNotifyStateUpdate = false, int steps = 1) { for (int i = 0; i < steps; i++) { Undo(true); } if (!doNotNotifyStateUpdate) UndoRedoStateChanged?.Invoke(this,null); } /// /// 多步重做 /// /// 步长 public void Redo(bool doNotNotifyStateUpdate = false, int steps = 1) { for (int i = 0; i < steps; i++) { Redo(true); } if (!doNotNotifyStateUpdate) UndoRedoStateChanged?.Invoke(this,null); } public void Redo() { Redo(false); } /// /// 重做 /// public void Redo(bool doNotNotifyStateUpdate = false) { if (!(_actions.Count - _currentIndex > 0)) return; if (_asyncPerformerLocker) return; var item = _actions[Math.Min(_currentIndex, _actions.Count - 1)]; if ((new TimeMachineActionType[] { TimeMachineActionType.Test, }).Contains(item.ActionType)) { (item as TimeMachineTestAction).IsReversed = false; (item as TimeMachineTestAction).Perform(); } var index = Math.Min(Math.Max(_currentIndex + 1, 0), _actions.Count); _currentIndex = index; if (!doNotNotifyStateUpdate) UndoRedoStateChanged?.Invoke(this,null); } } }