832 lines
31 KiB
C#
832 lines
31 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Ink;
|
|
using System.Windows.Input;
|
|
using System.Windows.Media;
|
|
using System.Windows.Media.Animation;
|
|
using System.Windows.Shapes;
|
|
using System.Windows.Threading;
|
|
|
|
namespace Ink_Canvas.Helpers
|
|
{
|
|
/// <summary>
|
|
/// 墨迹渐隐管理器 - 管理墨迹的渐隐动画和状态
|
|
/// </summary>
|
|
public class InkFadeManager
|
|
{
|
|
#region Properties
|
|
/// <summary>
|
|
/// 是否启用墨迹渐隐功能
|
|
/// </summary>
|
|
public bool IsEnabled { get; set; }
|
|
|
|
/// <summary>
|
|
/// 墨迹渐隐时间(毫秒)
|
|
/// </summary>
|
|
public int FadeTime { get; set; } = 3000;
|
|
|
|
/// <summary>
|
|
/// 渐隐动画持续时间(毫秒)
|
|
/// </summary>
|
|
public int AnimationDuration { get; set; } = 1000;
|
|
#endregion
|
|
|
|
#region Private Fields
|
|
private readonly MainWindow _mainWindow;
|
|
private readonly Dispatcher _dispatcher;
|
|
private readonly Dictionary<Stroke, DispatcherTimer> _fadeTimers;
|
|
private readonly Dictionary<Stroke, UIElement> _strokeVisuals;
|
|
private readonly Dictionary<Stroke, Point> _strokeStartPoints;
|
|
private readonly Dictionary<Stroke, Point> _strokeEndPoints;
|
|
#endregion
|
|
|
|
#region Constructor
|
|
public InkFadeManager(MainWindow mainWindow)
|
|
{
|
|
_mainWindow = mainWindow ?? throw new ArgumentNullException(nameof(mainWindow));
|
|
_dispatcher = _mainWindow.Dispatcher;
|
|
_fadeTimers = new Dictionary<Stroke, DispatcherTimer>();
|
|
_strokeVisuals = new Dictionary<Stroke, UIElement>();
|
|
_strokeStartPoints = new Dictionary<Stroke, Point>();
|
|
_strokeEndPoints = new Dictionary<Stroke, Point>();
|
|
}
|
|
#endregion
|
|
|
|
#region Public Methods
|
|
/// <summary>
|
|
/// 添加需要渐隐的墨迹
|
|
/// </summary>
|
|
/// <param name="stroke">墨迹对象</param>
|
|
/// <param name="startPoint">落笔点</param>
|
|
/// <param name="endPoint">抬笔点</param>
|
|
public void AddFadingStroke(Stroke stroke, Point startPoint, Point endPoint)
|
|
{
|
|
if (!IsEnabled || stroke == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
|
|
// 记录墨迹的起点和终点
|
|
_strokeStartPoints[stroke] = startPoint;
|
|
_strokeEndPoints[stroke] = endPoint;
|
|
|
|
// 创建墨迹的视觉元素(湿墨迹状态)
|
|
var strokeVisual = CreateStrokeVisual(stroke);
|
|
if (strokeVisual == null) return;
|
|
|
|
_strokeVisuals[stroke] = strokeVisual;
|
|
|
|
// 创建定时器,在指定时间后开始渐隐动画
|
|
var timer = new DispatcherTimer
|
|
{
|
|
Interval = TimeSpan.FromMilliseconds(FadeTime)
|
|
};
|
|
|
|
timer.Tick += (sender, e) =>
|
|
{
|
|
StartFadeAnimation(stroke);
|
|
timer.Stop();
|
|
_fadeTimers.Remove(stroke);
|
|
};
|
|
|
|
_fadeTimers[stroke] = timer;
|
|
timer.Start();
|
|
|
|
// 将视觉元素添加到画布上
|
|
_dispatcher.InvokeAsync(() =>
|
|
{
|
|
try
|
|
{
|
|
if (_mainWindow.inkCanvas != null)
|
|
{
|
|
// 将墨迹添加到 inkCanvas 的父容器中,而不是 inkCanvas.Children
|
|
// 这样可以避免坐标系统问题
|
|
var parent = _mainWindow.inkCanvas.Parent as Panel;
|
|
if (parent != null)
|
|
{
|
|
parent.Children.Add(strokeVisual);
|
|
}
|
|
else
|
|
{
|
|
// 如果无法获取父容器,则添加到 inkCanvas.Children
|
|
_mainWindow.inkCanvas.Children.Add(strokeVisual);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogHelper.WriteLogToFile($"添加墨迹视觉元素到画布失败: {ex}", LogHelper.LogType.Error);
|
|
}
|
|
});
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogHelper.WriteLogToFile($"添加渐隐墨迹失败: {ex}", LogHelper.LogType.Error);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 移除墨迹
|
|
/// </summary>
|
|
/// <param name="stroke">要移除的墨迹</param>
|
|
public void RemoveStroke(Stroke stroke)
|
|
{
|
|
if (stroke == null) return;
|
|
|
|
try
|
|
{
|
|
if (_fadeTimers.TryGetValue(stroke, out var timer))
|
|
{
|
|
timer.Stop();
|
|
_fadeTimers.Remove(stroke);
|
|
}
|
|
|
|
if (_strokeVisuals.TryGetValue(stroke, out var visual))
|
|
{
|
|
_dispatcher.InvokeAsync(() =>
|
|
{
|
|
try
|
|
{
|
|
// 从父容器中移除墨迹
|
|
var parent = _mainWindow.inkCanvas?.Parent as Panel;
|
|
if (parent != null && parent.Children.Contains(visual))
|
|
{
|
|
parent.Children.Remove(visual);
|
|
}
|
|
else if (_mainWindow.inkCanvas != null && _mainWindow.inkCanvas.Children.Contains(visual))
|
|
{
|
|
_mainWindow.inkCanvas.Children.Remove(visual);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogHelper.WriteLogToFile($"从画布移除墨迹视觉元素失败: {ex}", LogHelper.LogType.Error);
|
|
}
|
|
});
|
|
|
|
_strokeVisuals.Remove(stroke);
|
|
}
|
|
|
|
_strokeStartPoints.Remove(stroke);
|
|
_strokeEndPoints.Remove(stroke);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogHelper.WriteLogToFile($"移除渐隐墨迹失败: {ex}", LogHelper.LogType.Error);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 清除所有渐隐墨迹
|
|
/// </summary>
|
|
public void ClearAllFadingStrokes()
|
|
{
|
|
try
|
|
{
|
|
foreach (var timer in _fadeTimers.Values)
|
|
{
|
|
timer.Stop();
|
|
}
|
|
|
|
_fadeTimers.Clear();
|
|
|
|
_dispatcher.InvokeAsync(() =>
|
|
{
|
|
try
|
|
{
|
|
if (_mainWindow.inkCanvas != null)
|
|
{
|
|
var parent = _mainWindow.inkCanvas.Parent as Panel;
|
|
foreach (var visual in _strokeVisuals.Values)
|
|
{
|
|
if (parent != null && parent.Children.Contains(visual))
|
|
{
|
|
parent.Children.Remove(visual);
|
|
}
|
|
else if (_mainWindow.inkCanvas.Children.Contains(visual))
|
|
{
|
|
_mainWindow.inkCanvas.Children.Remove(visual);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogHelper.WriteLogToFile($"清除所有墨迹视觉元素失败: {ex}", LogHelper.LogType.Error);
|
|
}
|
|
});
|
|
|
|
_strokeVisuals.Clear();
|
|
_strokeStartPoints.Clear();
|
|
_strokeEndPoints.Clear();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogHelper.WriteLogToFile($"清除所有渐隐墨迹失败: {ex}", LogHelper.LogType.Error);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 更新渐隐时间设置
|
|
/// </summary>
|
|
/// <param name="fadeTime">新的渐隐时间(毫秒)</param>
|
|
public void UpdateFadeTime(int fadeTime)
|
|
{
|
|
FadeTime = fadeTime;
|
|
|
|
foreach (var kvp in _fadeTimers)
|
|
{
|
|
var stroke = kvp.Key;
|
|
var timer = kvp.Value;
|
|
|
|
timer.Stop();
|
|
timer.Interval = TimeSpan.FromMilliseconds(FadeTime);
|
|
timer.Start();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// 启用墨迹渐隐功能
|
|
/// </summary>
|
|
public void Enable()
|
|
{
|
|
IsEnabled = true;
|
|
LogHelper.WriteLogToFile("墨迹渐隐功能已启用");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 禁用墨迹渐隐功能
|
|
/// </summary>
|
|
public void Disable()
|
|
{
|
|
IsEnabled = false;
|
|
LogHelper.WriteLogToFile("墨迹渐隐功能已禁用");
|
|
}
|
|
#endregion
|
|
|
|
#region Private Methods
|
|
/// <summary>
|
|
/// 创建墨迹的视觉元素
|
|
/// </summary>
|
|
/// <param name="stroke">墨迹对象</param>
|
|
/// <returns>视觉元素</returns>
|
|
private UIElement CreateStrokeVisual(Stroke stroke)
|
|
{
|
|
try
|
|
{
|
|
// 创建路径几何,使用墨迹的实际位置
|
|
var geometry = stroke.GetGeometry();
|
|
if (geometry == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// 获取绘画属性
|
|
var drawingAttribs = stroke.DrawingAttributes;
|
|
|
|
// 创建路径元素,确保使用正确的绘画属性
|
|
var path = new Path
|
|
{
|
|
Data = geometry,
|
|
Stroke = new SolidColorBrush(drawingAttribs.Color),
|
|
StrokeThickness = drawingAttribs.Width, // 使用原始墨迹的粗细
|
|
StrokeStartLineCap = PenLineCap.Round,
|
|
StrokeEndLineCap = PenLineCap.Round,
|
|
StrokeLineJoin = PenLineJoin.Round,
|
|
Fill = drawingAttribs.IsHighlighter ? new SolidColorBrush(drawingAttribs.Color) : null, // 高亮笔需要填充
|
|
Opacity = 0.95, // 初始透明度更高,显得更自然
|
|
|
|
// 优化渲染质量
|
|
UseLayoutRounding = false,
|
|
SnapsToDevicePixels = false
|
|
};
|
|
|
|
// 如果是高亮笔,调整透明度和混合模式
|
|
if (drawingAttribs.IsHighlighter)
|
|
{
|
|
path.Opacity = 0.4; // 高亮笔初始透明度更低,更符合荧光笔特性
|
|
|
|
// 为高亮笔添加特殊的混合效果
|
|
// 使用更柔和的笔触样式
|
|
path.StrokeStartLineCap = PenLineCap.Flat;
|
|
path.StrokeEndLineCap = PenLineCap.Flat;
|
|
path.StrokeLineJoin = PenLineJoin.Miter;
|
|
|
|
// 高亮笔通常需要更宽的笔触来覆盖下面的内容
|
|
if (drawingAttribs.Width < 20)
|
|
{
|
|
path.StrokeThickness = Math.Max(drawingAttribs.Width * 1.5, 20);
|
|
}
|
|
}
|
|
|
|
// 不设置任何变换,保持墨迹原有粗细
|
|
var bounds = geometry.Bounds;
|
|
|
|
// 设置墨迹的初始位置
|
|
System.Windows.Controls.Canvas.SetLeft(path, bounds.Left);
|
|
System.Windows.Controls.Canvas.SetTop(path, bounds.Top);
|
|
|
|
return path;
|
|
}
|
|
catch (Exception)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 开始渐隐动画
|
|
/// </summary>
|
|
/// <param name="stroke">要渐隐的墨迹</param>
|
|
private void StartFadeAnimation(Stroke stroke)
|
|
{
|
|
if (!_strokeVisuals.TryGetValue(stroke, out var visual)) return;
|
|
|
|
try
|
|
{
|
|
_dispatcher.InvokeAsync(() =>
|
|
{
|
|
// 获取当前透明度和判断是否为高亮笔
|
|
var currentOpacity = visual.Opacity;
|
|
var isHighlighter = stroke.DrawingAttributes.IsHighlighter;
|
|
|
|
// 根据墨迹类型选择不同的动画效果
|
|
if (isHighlighter)
|
|
{
|
|
StartHighlighterFadeAnimation(visual, stroke, currentOpacity);
|
|
}
|
|
else
|
|
{
|
|
StartNormalStrokeFadeAnimation(visual, stroke, currentOpacity);
|
|
}
|
|
});
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogHelper.WriteLogToFile($"开始渐隐动画失败: {ex}", LogHelper.LogType.Error);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 开始普通墨迹的渐隐动画
|
|
/// </summary>
|
|
private void StartNormalStrokeFadeAnimation(UIElement visual, Stroke stroke, double currentOpacity)
|
|
{
|
|
try
|
|
{
|
|
StartProgressiveFadeAnimation(visual, stroke, currentOpacity, AnimationDuration);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogHelper.WriteLogToFile($"开始普通墨迹渐隐动画失败: {ex}", LogHelper.LogType.Error);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 开始高亮笔的渐隐动画
|
|
/// </summary>
|
|
private void StartHighlighterFadeAnimation(UIElement visual, Stroke stroke, double currentOpacity)
|
|
{
|
|
try
|
|
{
|
|
StartProgressiveFadeAnimation(visual, stroke, currentOpacity, (int)(AnimationDuration * 1.5));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogHelper.WriteLogToFile($"开始高亮笔渐隐动画失败: {ex}", LogHelper.LogType.Error);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 渐进式渐隐动画 - 从起点到终点逐渐消失
|
|
/// </summary>
|
|
private void StartProgressiveFadeAnimation(UIElement visual, Stroke stroke, double currentOpacity, int duration)
|
|
{
|
|
try
|
|
{
|
|
// 确保所有墨迹都能显示动画,包括短墨迹
|
|
if (stroke.StylusPoints.Count < 2)
|
|
{
|
|
// 只有1个点的墨迹也使用分段动画,确保视觉效果
|
|
CreateSegmentedStroke(visual, stroke, currentOpacity, duration);
|
|
return;
|
|
}
|
|
|
|
// 将墨迹分段并创建多个 Path
|
|
CreateSegmentedStroke(visual, stroke, currentOpacity, duration);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogHelper.WriteLogToFile($"渐进式渐隐动画失败: {ex}", LogHelper.LogType.Error);
|
|
// 失败时回退到简单动画
|
|
StartSimpleFadeAnimation(visual, stroke, currentOpacity, duration);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 创建分段墨迹并开始渐进消失
|
|
/// </summary>
|
|
private void CreateSegmentedStroke(UIElement originalVisual, Stroke stroke, double opacity, int duration)
|
|
{
|
|
try
|
|
{
|
|
var stylusPoints = stroke.StylusPoints;
|
|
var totalPoints = stylusPoints.Count;
|
|
|
|
// 分段算法 - 确保所有墨迹都有足够的动画效果
|
|
var strokeLength = CalculateStrokeLength(stylusPoints);
|
|
var segmentCount = CalculateOptimalSegmentCount(totalPoints, strokeLength);
|
|
|
|
// 强制最小分段数量,确保短墨迹也有动画效果
|
|
segmentCount = Math.Max(segmentCount, 4);
|
|
|
|
var pointsPerSegment = Math.Max(1, totalPoints / segmentCount);
|
|
|
|
// 隐藏原始视觉元素
|
|
originalVisual.Visibility = Visibility.Hidden;
|
|
|
|
var segments = new List<UIElement>();
|
|
var parent = _mainWindow.inkCanvas?.Parent as Panel;
|
|
if (parent == null)
|
|
{
|
|
// 如果父容器不是Panel,直接使用InkCanvas
|
|
parent = null; // 稍后会检查并使用InkCanvas.Children
|
|
}
|
|
|
|
// 创建各个分段 - 确保短墨迹也能正确分段
|
|
for (int i = 0; i < segmentCount; i++)
|
|
{
|
|
var startIndex = i * pointsPerSegment;
|
|
var endIndex = (i == segmentCount - 1) ? totalPoints - 1 : (i + 1) * pointsPerSegment;
|
|
|
|
// 确保有足够的点来创建分段,对于短墨迹特殊处理
|
|
if (endIndex <= startIndex && totalPoints > 1)
|
|
{
|
|
// 短墨迹:每个点作为一个分段
|
|
startIndex = i;
|
|
endIndex = Math.Min(i + 1, totalPoints - 1);
|
|
}
|
|
|
|
// 为每个分段添加重叠,确保连接处平滑
|
|
var overlap = Math.Max(1, pointsPerSegment / 6); // 15%的重叠,平衡平滑与速度
|
|
var actualStartIndex = Math.Max(0, startIndex - overlap);
|
|
var actualEndIndex = Math.Min(totalPoints - 1, endIndex + overlap);
|
|
|
|
var segment = CreateStrokeSegment(stroke, actualStartIndex, actualEndIndex, opacity);
|
|
if (segment != null)
|
|
{
|
|
segments.Add(segment);
|
|
if (parent != null)
|
|
{
|
|
parent.Children.Add(segment);
|
|
}
|
|
else if (_mainWindow.inkCanvas != null)
|
|
{
|
|
_mainWindow.inkCanvas.Children.Add(segment);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 开始分段渐隐动画
|
|
StartSegmentedFadeAnimation(segments, stroke, originalVisual, duration);
|
|
}
|
|
catch (Exception)
|
|
{
|
|
StartSimpleFadeAnimation(originalVisual, stroke, opacity, duration);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 创建墨迹分段
|
|
/// </summary>
|
|
private UIElement CreateStrokeSegment(Stroke originalStroke, int startIndex, int endIndex, double opacity)
|
|
{
|
|
try
|
|
{
|
|
// 创建分段的 StylusPoint 集合
|
|
var segmentPoints = new StylusPointCollection();
|
|
for (int i = startIndex; i <= endIndex && i < originalStroke.StylusPoints.Count; i++)
|
|
{
|
|
segmentPoints.Add(originalStroke.StylusPoints[i]);
|
|
}
|
|
|
|
if (segmentPoints.Count < 2) return null;
|
|
|
|
// 创建分段墨迹
|
|
var segmentStroke = new Stroke(segmentPoints)
|
|
{
|
|
DrawingAttributes = originalStroke.DrawingAttributes.Clone()
|
|
};
|
|
|
|
// 创建分段的视觉元素
|
|
var geometry = segmentStroke.GetGeometry();
|
|
if (geometry == null) return null;
|
|
|
|
var drawingAttribs = segmentStroke.DrawingAttributes;
|
|
var path = new Path
|
|
{
|
|
Data = geometry,
|
|
Stroke = new SolidColorBrush(drawingAttribs.Color),
|
|
StrokeThickness = drawingAttribs.Width,
|
|
StrokeStartLineCap = drawingAttribs.IsHighlighter ? PenLineCap.Flat : PenLineCap.Round,
|
|
StrokeEndLineCap = drawingAttribs.IsHighlighter ? PenLineCap.Flat : PenLineCap.Round,
|
|
StrokeLineJoin = drawingAttribs.IsHighlighter ? PenLineJoin.Miter : PenLineJoin.Round,
|
|
Fill = drawingAttribs.IsHighlighter ? new SolidColorBrush(drawingAttribs.Color) : null,
|
|
Opacity = opacity,
|
|
UseLayoutRounding = false,
|
|
SnapsToDevicePixels = false
|
|
};
|
|
|
|
// 设置位置
|
|
var bounds = geometry.Bounds;
|
|
System.Windows.Controls.Canvas.SetLeft(path, bounds.Left);
|
|
System.Windows.Controls.Canvas.SetTop(path, bounds.Top);
|
|
|
|
return path;
|
|
}
|
|
catch (Exception)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 开始分段渐隐动画
|
|
/// </summary>
|
|
private void StartSegmentedFadeAnimation(List<UIElement> segments, Stroke originalStroke, UIElement originalVisual, int totalDuration)
|
|
{
|
|
try
|
|
{
|
|
// 动画时序算法
|
|
var segmentDuration = CalculateOptimalSegmentDuration(totalDuration, segments.Count);
|
|
var animationCurve = CreateAppleStyleAnimationCurve(segments.Count, totalDuration);
|
|
|
|
// 跟踪动画完成状态
|
|
var completedSegments = new HashSet<UIElement>();
|
|
var totalSegments = segments.Count;
|
|
|
|
// 渐隐效果 - 使用自然的动画曲线
|
|
for (int i = 0; i < segments.Count; i++)
|
|
{
|
|
var segment = segments[i];
|
|
|
|
// 使用预计算的动画曲线获取延迟时间
|
|
var delay = animationCurve[i];
|
|
|
|
// 使用定时器延迟启动每个分段的动画
|
|
var timer = new DispatcherTimer
|
|
{
|
|
Interval = TimeSpan.FromMilliseconds(delay)
|
|
};
|
|
|
|
int segmentIndex = i; // 捕获当前索引
|
|
timer.Tick += (sender, e) =>
|
|
{
|
|
StartSingleSegmentFadeAnimation(segment, segmentDuration, () =>
|
|
{
|
|
// 动画完成回调
|
|
lock (completedSegments)
|
|
{
|
|
completedSegments.Add(segment);
|
|
|
|
// 检查是否所有分段都完成了
|
|
if (completedSegments.Count >= totalSegments)
|
|
{
|
|
CleanupSegmentedAnimation(segments, originalStroke, originalVisual);
|
|
}
|
|
}
|
|
});
|
|
timer.Stop();
|
|
};
|
|
|
|
timer.Start();
|
|
}
|
|
|
|
// 设置一个安全超时定时器,防止无限等待
|
|
var safetyTimeout = totalDuration + (segments.Count * segmentDuration) + 1200; // 额外1.2秒缓冲,确保动画完整
|
|
var safetyTimer = new DispatcherTimer
|
|
{
|
|
Interval = TimeSpan.FromMilliseconds(safetyTimeout)
|
|
};
|
|
|
|
safetyTimer.Tick += (sender, e) =>
|
|
{
|
|
CleanupSegmentedAnimation(segments, originalStroke, originalVisual);
|
|
safetyTimer.Stop();
|
|
};
|
|
|
|
safetyTimer.Start();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogHelper.WriteLogToFile($"分段渐隐动画失败: {ex}", LogHelper.LogType.Error);
|
|
CleanupSegmentedAnimation(segments, originalStroke, originalVisual);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 单个分段的渐隐动画
|
|
/// </summary>
|
|
private void StartSingleSegmentFadeAnimation(UIElement segment, int duration, Action onCompleted = null)
|
|
{
|
|
try
|
|
{
|
|
// 只使用透明度动画,保持墨迹原有粗细
|
|
var fadeAnimation = new DoubleAnimation
|
|
{
|
|
From = segment.Opacity,
|
|
To = 0.0,
|
|
Duration = TimeSpan.FromMilliseconds(duration),
|
|
EasingFunction = new CubicEase { EasingMode = EasingMode.EaseInOut } // 更平滑的缓动
|
|
};
|
|
|
|
// 添加动画完成事件
|
|
if (onCompleted != null)
|
|
{
|
|
fadeAnimation.Completed += (sender, e) =>
|
|
{
|
|
onCompleted?.Invoke();
|
|
};
|
|
}
|
|
|
|
// 只应用透明度动画,不改变墨迹大小
|
|
segment.BeginAnimation(UIElement.OpacityProperty, fadeAnimation);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogHelper.WriteLogToFile($"单个分段渐隐动画失败: {ex}", LogHelper.LogType.Error);
|
|
// 即使失败也要调用完成回调
|
|
onCompleted?.Invoke();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 清理分段动画
|
|
/// </summary>
|
|
private void CleanupSegmentedAnimation(List<UIElement> segments, Stroke originalStroke, UIElement originalVisual)
|
|
{
|
|
try
|
|
{
|
|
// 移除所有分段
|
|
var parent = _mainWindow.inkCanvas?.Parent as Panel;
|
|
|
|
foreach (var segment in segments)
|
|
{
|
|
if (parent != null && parent.Children.Contains(segment))
|
|
{
|
|
parent.Children.Remove(segment);
|
|
}
|
|
else if (_mainWindow.inkCanvas != null && _mainWindow.inkCanvas.Children.Contains(segment))
|
|
{
|
|
_mainWindow.inkCanvas.Children.Remove(segment);
|
|
}
|
|
}
|
|
|
|
// 清理原始墨迹
|
|
OnAnimationCompleted(originalVisual, originalStroke);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogHelper.WriteLogToFile($"清理分段动画失败: {ex}", LogHelper.LogType.Error);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 简单渐隐动画(备用方案)
|
|
/// </summary>
|
|
private void StartSimpleFadeAnimation(UIElement visual, Stroke stroke, double currentOpacity, int duration)
|
|
{
|
|
try
|
|
{
|
|
var fadeAnimation = new DoubleAnimation
|
|
{
|
|
From = currentOpacity,
|
|
To = 0.0,
|
|
Duration = TimeSpan.FromMilliseconds(duration),
|
|
EasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseIn }
|
|
};
|
|
|
|
fadeAnimation.Completed += (sender, e) => OnAnimationCompleted(visual, stroke);
|
|
visual.BeginAnimation(UIElement.OpacityProperty, fadeAnimation);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogHelper.WriteLogToFile($"简单渐隐动画失败: {ex}", LogHelper.LogType.Error);
|
|
OnAnimationCompleted(visual, stroke);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 计算墨迹的实际长度
|
|
/// </summary>
|
|
private double CalculateStrokeLength(StylusPointCollection points)
|
|
{
|
|
if (points.Count < 2) return 0;
|
|
|
|
double totalLength = 0;
|
|
for (int i = 1; i < points.Count; i++)
|
|
{
|
|
var p1 = points[i - 1].ToPoint();
|
|
var p2 = points[i].ToPoint();
|
|
totalLength += Math.Sqrt(Math.Pow(p2.X - p1.X, 2) + Math.Pow(p2.Y - p1.Y, 2));
|
|
}
|
|
return totalLength;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 根据墨迹特性计算最优分段数量 - 平衡速度与完整性
|
|
/// </summary>
|
|
private int CalculateOptimalSegmentCount(int pointCount, double strokeLength)
|
|
{
|
|
// 平衡速度与完整性,确保动画效果的同时提高速度
|
|
const double PIXELS_PER_SEGMENT = 12.0; // 每段适中长度,平衡效果与速度
|
|
const int MIN_SEGMENTS = 5; // 适当的最小分段数,确保动画效果
|
|
const int MAX_SEGMENTS = 100; // 适中的最大分段数,平衡性能与效果
|
|
|
|
// 根据长度计算基础分段数
|
|
var lengthBasedSegments = Math.Max(MIN_SEGMENTS, (int)(strokeLength / PIXELS_PER_SEGMENT));
|
|
|
|
// 根据点密度调整,平衡效果与速度
|
|
var density = pointCount > 0 ? strokeLength / pointCount : 1;
|
|
var densityFactor = Math.Max(0.4, Math.Min(2.5, density / 1.8));
|
|
|
|
var finalSegments = (int)(lengthBasedSegments * densityFactor);
|
|
|
|
// 对于短墨迹,确保至少有4个分段
|
|
if (pointCount <= 5)
|
|
{
|
|
finalSegments = Math.Max(finalSegments, 4);
|
|
}
|
|
|
|
// 限制在合理范围内
|
|
return Math.Min(MAX_SEGMENTS, Math.Max(MIN_SEGMENTS, finalSegments));
|
|
}
|
|
|
|
/// <summary>
|
|
/// 计算最优的单段动画持续时间 - 平衡速度与完整性
|
|
/// </summary>
|
|
private int CalculateOptimalSegmentDuration(int totalDuration, int segmentCount)
|
|
{
|
|
// 平衡速度与动画完整性
|
|
var baseDuration = totalDuration / Math.Max(segmentCount, 1);
|
|
var minDuration = 150; // 每段最少150ms,确保动画完整显示
|
|
var maxDuration = 500; // 每段最多500ms,平衡速度与完整性
|
|
|
|
return Math.Max(minDuration, Math.Min(maxDuration, baseDuration));
|
|
}
|
|
|
|
/// <summary>
|
|
/// 创建优化的动画时间曲线 - 平衡速度与完整性
|
|
/// </summary>
|
|
private int[] CreateAppleStyleAnimationCurve(int segmentCount, int totalDuration)
|
|
{
|
|
var curve = new int[segmentCount];
|
|
|
|
// 平衡速度与完整性,确保动画有足够时间播放
|
|
var availableTime = totalDuration * 0.6; // 使用60%的总时间,给动画留足够缓冲
|
|
var delayBetweenSegments = Math.Max(60, availableTime / Math.Max(segmentCount, 1));
|
|
|
|
for (int i = 0; i < segmentCount; i++)
|
|
{
|
|
// 线性延迟,确保每个分段都有足够时间
|
|
curve[i] = (int)(i * delayBetweenSegments);
|
|
}
|
|
|
|
return curve;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 动画完成后的统一处理
|
|
/// </summary>
|
|
private void OnAnimationCompleted(UIElement visual, Stroke stroke)
|
|
{
|
|
try
|
|
{
|
|
// 从父容器中移除墨迹
|
|
var parent = _mainWindow.inkCanvas?.Parent as Panel;
|
|
if (parent != null && parent.Children.Contains(visual))
|
|
{
|
|
parent.Children.Remove(visual);
|
|
}
|
|
else if (_mainWindow.inkCanvas != null && _mainWindow.inkCanvas.Children.Contains(visual))
|
|
{
|
|
_mainWindow.inkCanvas.Children.Remove(visual);
|
|
}
|
|
|
|
RemoveStroke(stroke);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogHelper.WriteLogToFile($"渐隐动画完成后清理墨迹失败: {ex}", LogHelper.LogType.Error);
|
|
}
|
|
}
|
|
#endregion
|
|
}
|
|
} |