303 lines
10 KiB
C#
303 lines
10 KiB
C#
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 {
|
||
|
||
/// <summary>
|
||
/// 指示时光机的一个Action的类型
|
||
/// </summary>
|
||
public enum TimeMachineActionType {
|
||
/// <summary>
|
||
/// 用户绘制的墨迹被添加
|
||
/// </summary>
|
||
UserInputStrokeAdded,
|
||
/// <summary>
|
||
/// 墨迹被添加,这可能是由软件添加的
|
||
/// </summary>
|
||
StrokeAdded,
|
||
/// <summary>
|
||
/// 墨迹被移除
|
||
/// </summary>
|
||
StrokeRemoved,
|
||
/// <summary>
|
||
/// 用户使用了面积擦进行擦除
|
||
/// </summary>
|
||
UserGeometryErased,
|
||
/// <summary>
|
||
/// 用户使用了墨迹擦进行擦除
|
||
/// </summary>
|
||
UserStrokeErased,
|
||
/// <summary>
|
||
/// 用户使用了墨迹擦进行擦除
|
||
/// </summary>
|
||
UserAreaErased,
|
||
Test
|
||
}
|
||
|
||
public class TimeMachineAction {
|
||
/// <summary>
|
||
/// Action的类型
|
||
/// </summary>
|
||
public TimeMachineActionType ActionType { get; protected set ; }
|
||
|
||
private bool _isReversed = false;
|
||
|
||
public event EventHandler ActionIsReversedChanged;
|
||
|
||
/// <summary>
|
||
/// 指示该Action是否被逆操作了。
|
||
/// 比如现在如果这是一个StrokeAdded的Action,当执行撤销后,墨迹消失,此时IsReversed应该为True。
|
||
/// </summary>
|
||
public bool IsReversed {
|
||
get => _isReversed;
|
||
set {
|
||
if (value == _isReversed) return;
|
||
_isReversed = value;
|
||
ActionIsReversedChanged?.Invoke(this,null);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 墨迹操作的Action
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 墨迹。如果为UserInputStrokeAdded或StrokeAdded则为添加的墨迹,否则,StrokeRemoved就是被移除的墨迹。
|
||
/// </summary>
|
||
public IccStroke Stroke { get; private set; }
|
||
|
||
/// <summary>
|
||
/// 执行这个操作的时间戳,-1为没有时间。
|
||
/// </summary>
|
||
public long TimeStamp { get; private set; } = -1;
|
||
|
||
/// <summary>
|
||
/// 应用该Action所导致的更改到画布上,需要指定Dispatcher,StrokeCollection
|
||
/// </summary>
|
||
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);
|
||
};
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// only internal usage
|
||
/// </summary>
|
||
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}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 基于 icc 修改的 时光机 V2 版本,提供了更精简的设计
|
||
/// </summary>
|
||
public class TimeMachineV2 {
|
||
|
||
/// <summary>
|
||
/// 当前的Index
|
||
/// </summary>
|
||
private int _currentIndex = 0;
|
||
|
||
public int CurrentIndex => _currentIndex;
|
||
|
||
private List<TimeMachineAction> _actions = new List<TimeMachineAction>();
|
||
|
||
public TimeMachineAction[] Actions => _actions.ToArray();
|
||
|
||
public int ActionsCount => _actions.Count;
|
||
|
||
/// <summary>
|
||
/// 在撤销重做状态发生改变时触发
|
||
/// </summary>
|
||
public event EventHandler UndoRedoStateChanged;
|
||
|
||
/// <summary>
|
||
/// 执行action的perform的时候用于锁住时光机
|
||
/// </summary>
|
||
private bool _asyncPerformerLocker = false;
|
||
|
||
private void asyncPerformerLockedOrUnLocked(object sender, EventArgs e) {
|
||
if (sender is TimeMachineStrokeAction) {
|
||
_asyncPerformerLocker = (sender as TimeMachineStrokeAction).IsLocked;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 丢弃指针Index后的所有Actions并销毁锁事件
|
||
/// </summary>
|
||
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);
|
||
|
||
}
|
||
|
||
/// <summary>
|
||
/// only internal usage
|
||
/// </summary>
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 撤销
|
||
/// </summary>
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 多步撤销
|
||
/// </summary>
|
||
/// <param name="steps">步长</param>
|
||
public void Undo(bool doNotNotifyStateUpdate = false, int steps = 1) {
|
||
for (int i = 0; i < steps; i++) {
|
||
Undo(true);
|
||
}
|
||
|
||
if (!doNotNotifyStateUpdate) UndoRedoStateChanged?.Invoke(this,null);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 多步重做
|
||
/// </summary>
|
||
/// <param name="steps">步长</param>
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 重做
|
||
/// </summary>
|
||
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);
|
||
}
|
||
}
|
||
}
|