From 5665fcc8230a0f715c698045149f133ba1ae568b Mon Sep 17 00:00:00 2001 From: CJKmkp <2564608840@qq.com> Date: Sat, 23 Aug 2025 23:13:39 +0800 Subject: [PATCH] add:issue #131 --- Ink Canvas/Helpers/InkFadeManager.cs | 683 +++++++++++++++++- Ink Canvas/MainWindow.xaml | 88 ++- Ink Canvas/MainWindow.xaml.cs | 110 +++ Ink Canvas/MainWindow_cs/MW_Settings.cs | 108 ++- Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs | 63 +- .../MW_SimulatePressure&InkToShape.cs | 30 + Ink Canvas/MainWindow_cs/MW_TouchEvents.cs | 23 +- Ink Canvas/Resources/Settings.cs | 7 + 8 files changed, 1019 insertions(+), 93 deletions(-) diff --git a/Ink Canvas/Helpers/InkFadeManager.cs b/Ink Canvas/Helpers/InkFadeManager.cs index 6ddbc874..df935494 100644 --- a/Ink Canvas/Helpers/InkFadeManager.cs +++ b/Ink Canvas/Helpers/InkFadeManager.cs @@ -1,9 +1,14 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Windows; using System.Windows.Media.Animation; using System.Windows.Threading; using System.Windows.Ink; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Shapes; namespace Ink_Canvas.Helpers { @@ -34,6 +39,8 @@ namespace Ink_Canvas.Helpers private readonly Dispatcher _dispatcher; private readonly Dictionary _fadeTimers; private readonly Dictionary _strokeVisuals; + private readonly Dictionary _strokeStartPoints; + private readonly Dictionary _strokeEndPoints; #endregion #region Constructor @@ -43,6 +50,8 @@ namespace Ink_Canvas.Helpers _dispatcher = _mainWindow.Dispatcher; _fadeTimers = new Dictionary(); _strokeVisuals = new Dictionary(); + _strokeStartPoints = new Dictionary(); + _strokeEndPoints = new Dictionary(); } #endregion @@ -51,15 +60,31 @@ namespace Ink_Canvas.Helpers /// 添加需要渐隐的墨迹 /// /// 墨迹对象 - /// 墨迹的视觉元素 - public void AddFadingStroke(Stroke stroke, UIElement visual) + /// 落笔点 + /// 抬笔点 + public void AddFadingStroke(Stroke stroke, Point startPoint, Point endPoint) { - if (!IsEnabled || stroke == null || visual == null) return; + LogHelper.WriteLogToFile($"AddFadingStroke 被调用,IsEnabled: {IsEnabled}, stroke: {(stroke != null ? "非空" : "空")}", LogHelper.LogType.Info); + + if (!IsEnabled || stroke == null) + { + LogHelper.WriteLogToFile($"AddFadingStroke 被拒绝,IsEnabled: {IsEnabled}, stroke: {(stroke != null ? "非空" : "空")}", LogHelper.LogType.Warning); + return; + } try { - // 记录墨迹和视觉元素的对应关系 - _strokeVisuals[stroke] = visual; + LogHelper.WriteLogToFile($"开始添加渐隐墨迹,渐隐时间:{FadeTime}ms,动画时间:{AnimationDuration}ms", LogHelper.LogType.Info); + + // 记录墨迹的起点和终点 + _strokeStartPoints[stroke] = startPoint; + _strokeEndPoints[stroke] = endPoint; + + // 创建墨迹的视觉元素(湿墨迹状态) + var strokeVisual = CreateStrokeVisual(stroke); + if (strokeVisual == null) return; + + _strokeVisuals[stroke] = strokeVisual; // 创建定时器,在指定时间后开始渐隐动画 var timer = new DispatcherTimer @@ -69,6 +94,7 @@ namespace Ink_Canvas.Helpers timer.Tick += (sender, e) => { + LogHelper.WriteLogToFile("定时器触发,开始渐隐动画", LogHelper.LogType.Info); StartFadeAnimation(stroke); timer.Stop(); _fadeTimers.Remove(stroke); @@ -77,6 +103,8 @@ namespace Ink_Canvas.Helpers _fadeTimers[stroke] = timer; timer.Start(); + LogHelper.WriteLogToFile($"定时器已启动,将在 {FadeTime}ms 后开始渐隐", LogHelper.LogType.Info); + // 将视觉元素添加到画布上 _dispatcher.InvokeAsync(() => { @@ -84,7 +112,20 @@ namespace Ink_Canvas.Helpers { if (_mainWindow.inkCanvas != null) { - _mainWindow.inkCanvas.Children.Add(visual); + // 将墨迹添加到 inkCanvas 的父容器中,而不是 inkCanvas.Children + // 这样可以避免坐标系统问题 + var parent = _mainWindow.inkCanvas.Parent as System.Windows.Controls.Panel; + if (parent != null) + { + parent.Children.Add(strokeVisual); + LogHelper.WriteLogToFile("墨迹已添加到父容器", LogHelper.LogType.Info); + } + else + { + // 如果无法获取父容器,则添加到 inkCanvas.Children + _mainWindow.inkCanvas.Children.Add(strokeVisual); + LogHelper.WriteLogToFile("墨迹已添加到 inkCanvas.Children", LogHelper.LogType.Info); + } } } catch (Exception ex) @@ -121,7 +162,13 @@ namespace Ink_Canvas.Helpers { try { - if (_mainWindow.inkCanvas != null && _mainWindow.inkCanvas.Children.Contains(visual)) + // 从父容器中移除墨迹 + var parent = _mainWindow.inkCanvas?.Parent as System.Windows.Controls.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); } @@ -134,6 +181,9 @@ namespace Ink_Canvas.Helpers _strokeVisuals.Remove(stroke); } + + _strokeStartPoints.Remove(stroke); + _strokeEndPoints.Remove(stroke); } catch (Exception ex) { @@ -161,9 +211,14 @@ namespace Ink_Canvas.Helpers { if (_mainWindow.inkCanvas != null) { + var parent = _mainWindow.inkCanvas.Parent as System.Windows.Controls.Panel; foreach (var visual in _strokeVisuals.Values) { - if (_mainWindow.inkCanvas.Children.Contains(visual)) + if (parent != null && parent.Children.Contains(visual)) + { + parent.Children.Remove(visual); + } + else if (_mainWindow.inkCanvas.Children.Contains(visual)) { _mainWindow.inkCanvas.Children.Remove(visual); } @@ -177,6 +232,8 @@ namespace Ink_Canvas.Helpers }); _strokeVisuals.Clear(); + _strokeStartPoints.Clear(); + _strokeEndPoints.Clear(); } catch (Exception ex) { @@ -203,17 +260,107 @@ namespace Ink_Canvas.Helpers } } + + /// - /// 更新动画持续时间设置 + /// 启用墨迹渐隐功能 /// - /// 新的动画持续时间(毫秒) - public void UpdateAnimationDuration(int animationDuration) + public void Enable() { - AnimationDuration = animationDuration; + IsEnabled = true; + LogHelper.WriteLogToFile("墨迹渐隐功能已启用", LogHelper.LogType.Info); + } + + /// + /// 禁用墨迹渐隐功能 + /// + public void Disable() + { + IsEnabled = false; + LogHelper.WriteLogToFile("墨迹渐隐功能已禁用", LogHelper.LogType.Info); } #endregion #region Private Methods + /// + /// 创建墨迹的视觉元素 + /// + /// 墨迹对象 + /// 视觉元素 + private UIElement CreateStrokeVisual(Stroke stroke) + { + try + { + LogHelper.WriteLogToFile("开始创建墨迹视觉元素", LogHelper.LogType.Info); + + // 创建路径几何,使用墨迹的实际位置 + var geometry = stroke.GetGeometry(); + if (geometry == null) + { + LogHelper.WriteLogToFile("墨迹几何为空,无法创建视觉元素", LogHelper.LogType.Error); + return null; + } + + LogHelper.WriteLogToFile($"墨迹几何边界:{geometry.Bounds}", LogHelper.LogType.Info); + + // 获取绘画属性 + 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); + } + + LogHelper.WriteLogToFile($"高亮笔特殊处理:透明度={path.Opacity},笔触宽度={path.StrokeThickness}", LogHelper.LogType.Info); + } + + // 不设置任何变换,保持墨迹原有粗细 + var bounds = geometry.Bounds; + + // 设置墨迹的初始位置 + System.Windows.Controls.Canvas.SetLeft(path, bounds.Left); + System.Windows.Controls.Canvas.SetTop(path, bounds.Top); + + LogHelper.WriteLogToFile($"墨迹视觉元素创建完成,位置:({bounds.Left}, {bounds.Top}),大小:{bounds.Width} x {bounds.Height},粗细:{drawingAttribs.Width},高亮笔:{drawingAttribs.IsHighlighter}", LogHelper.LogType.Info); + + return path; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"创建墨迹视觉元素失败: {ex}", LogHelper.LogType.Error); + return null; + } + } + /// /// 开始渐隐动画 /// @@ -224,34 +371,23 @@ namespace Ink_Canvas.Helpers try { + LogHelper.WriteLogToFile("开始执行渐隐动画", LogHelper.LogType.Info); + _dispatcher.InvokeAsync(() => { - var fadeAnimation = new DoubleAnimation + // 获取当前透明度和判断是否为高亮笔 + var currentOpacity = visual.Opacity; + var isHighlighter = stroke.DrawingAttributes.IsHighlighter; + + // 根据墨迹类型选择不同的动画效果 + if (isHighlighter) { - From = 1.0, - To = 0.0, - Duration = TimeSpan.FromMilliseconds(AnimationDuration), - EasingFunction = new CubicEase { EasingMode = EasingMode.EaseInOut } - }; - - fadeAnimation.Completed += (sender, e) => + StartHighlighterFadeAnimation(visual, stroke, currentOpacity); + } + else { - try - { - 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); - } - }; - - visual.BeginAnimation(UIElement.OpacityProperty, fadeAnimation); + StartNormalStrokeFadeAnimation(visual, stroke, currentOpacity); + } }); } catch (Exception ex) @@ -259,6 +395,481 @@ namespace Ink_Canvas.Helpers LogHelper.WriteLogToFile($"开始渐隐动画失败: {ex}", LogHelper.LogType.Error); } } + + /// + /// 开始普通墨迹的渐隐动画 + /// + private void StartNormalStrokeFadeAnimation(UIElement visual, Stroke stroke, double currentOpacity) + { + try + { + LogHelper.WriteLogToFile("开始普通墨迹渐进式渐隐动画", LogHelper.LogType.Info); + StartProgressiveFadeAnimation(visual, stroke, currentOpacity, AnimationDuration); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"开始普通墨迹渐隐动画失败: {ex}", LogHelper.LogType.Error); + } + } + + /// + /// 开始高亮笔的渐隐动画 + /// + private void StartHighlighterFadeAnimation(UIElement visual, Stroke stroke, double currentOpacity) + { + try + { + LogHelper.WriteLogToFile("开始高亮笔渐进式渐隐动画", LogHelper.LogType.Info); + StartProgressiveFadeAnimation(visual, stroke, currentOpacity, (int)(AnimationDuration * 1.5)); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"开始高亮笔渐隐动画失败: {ex}", LogHelper.LogType.Error); + } + } + + /// + /// 渐进式渐隐动画 - 从起点到终点逐渐消失 + /// + private void StartProgressiveFadeAnimation(UIElement visual, Stroke stroke, double currentOpacity, int duration) + { + try + { + LogHelper.WriteLogToFile($"开始渐进式渐隐动画,墨迹点数:{stroke.StylusPoints.Count}", LogHelper.LogType.Info); + + // 确保所有墨迹都能显示动画,包括短墨迹 + 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); + } + } + + /// + /// 创建分段墨迹并开始渐进消失 + /// + 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); + + LogHelper.WriteLogToFile($"创建 {segmentCount} 个分段,每段约 {pointsPerSegment} 个点", LogHelper.LogType.Info); + + // 隐藏原始视觉元素 + originalVisual.Visibility = Visibility.Hidden; + + var segments = new List(); + var parent = _mainWindow.inkCanvas?.Parent as System.Windows.Controls.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 ex) + { + LogHelper.WriteLogToFile($"创建分段墨迹失败: {ex}", LogHelper.LogType.Error); + StartSimpleFadeAnimation(originalVisual, stroke, opacity, duration); + } + } + + /// + /// 创建墨迹分段 + /// + 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 ex) + { + LogHelper.WriteLogToFile($"创建墨迹分段失败: {ex}", LogHelper.LogType.Error); + return null; + } + } + + /// + /// 开始分段渐隐动画 + /// + private void StartSegmentedFadeAnimation(List segments, Stroke originalStroke, UIElement originalVisual, int totalDuration) + { + try + { + LogHelper.WriteLogToFile($"开始 {segments.Count} 个分段的渐隐动画", LogHelper.LogType.Info); + + // 动画时序算法 + var segmentDuration = CalculateOptimalSegmentDuration(totalDuration, segments.Count); + var animationCurve = CreateAppleStyleAnimationCurve(segments.Count, totalDuration); + + // 跟踪动画完成状态 + var completedSegments = new HashSet(); + 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); + LogHelper.WriteLogToFile($"分段 {segmentIndex} 动画完成,已完成 {completedSegments.Count}/{totalSegments}", LogHelper.LogType.Info); + + // 检查是否所有分段都完成了 + if (completedSegments.Count >= totalSegments) + { + LogHelper.WriteLogToFile("所有分段动画完成,开始清理", LogHelper.LogType.Info); + 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) => + { + LogHelper.WriteLogToFile($"安全超时触发,强制清理动画", LogHelper.LogType.Warning); + CleanupSegmentedAnimation(segments, originalStroke, originalVisual); + safetyTimer.Stop(); + }; + + safetyTimer.Start(); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"分段渐隐动画失败: {ex}", LogHelper.LogType.Error); + CleanupSegmentedAnimation(segments, originalStroke, originalVisual); + } + } + + /// + /// 单个分段的渐隐动画 + /// + 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(); + } + } + + /// + /// 清理分段动画 + /// + private void CleanupSegmentedAnimation(List segments, Stroke originalStroke, UIElement originalVisual) + { + try + { + LogHelper.WriteLogToFile("清理分段渐隐动画", LogHelper.LogType.Info); + + // 移除所有分段 + var parent = _mainWindow.inkCanvas?.Parent as System.Windows.Controls.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); + } + } + + /// + /// 简单渐隐动画(备用方案) + /// + private void StartSimpleFadeAnimation(UIElement visual, Stroke stroke, double currentOpacity, int duration) + { + try + { + LogHelper.WriteLogToFile("使用简单渐隐动画", LogHelper.LogType.Info); + + 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); + } + } + + /// + /// 计算墨迹的实际长度 + /// + 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; + } + + /// + /// 根据墨迹特性计算最优分段数量 - 平衡速度与完整性 + /// + 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)); + } + + /// + /// 计算最优的单段动画持续时间 - 平衡速度与完整性 + /// + 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)); + } + + /// + /// 创建优化的动画时间曲线 - 平衡速度与完整性 + /// + 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; + } + + /// + /// 动画完成后的统一处理 + /// + private void OnAnimationCompleted(UIElement visual, Stroke stroke) + { + try + { + LogHelper.WriteLogToFile("渐隐动画完成,开始清理墨迹", LogHelper.LogType.Info); + + // 从父容器中移除墨迹 + var parent = _mainWindow.inkCanvas?.Parent as System.Windows.Controls.Panel; + if (parent != null && parent.Children.Contains(visual)) + { + parent.Children.Remove(visual); + LogHelper.WriteLogToFile("墨迹已从父容器移除", LogHelper.LogType.Info); + } + else if (_mainWindow.inkCanvas != null && _mainWindow.inkCanvas.Children.Contains(visual)) + { + _mainWindow.inkCanvas.Children.Remove(visual); + LogHelper.WriteLogToFile("墨迹已从 inkCanvas.Children 移除", LogHelper.LogType.Info); + } + + RemoveStroke(stroke); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"渐隐动画完成后清理墨迹失败: {ex}", LogHelper.LogType.Error); + } + } #endregion } } \ No newline at end of file diff --git a/Ink Canvas/MainWindow.xaml b/Ink Canvas/MainWindow.xaml index 9597d615..f9f94034 100644 --- a/Ink Canvas/MainWindow.xaml +++ b/Ink Canvas/MainWindow.xaml @@ -832,6 +832,27 @@ IsOn="True" FontFamily="Microsoft YaHei UI" FontWeight="Bold" Toggled="ToggleSwitchAdvancedBezierSmoothing_Toggled" /> + + + + + + + + + + + + @@ -1197,16 +1218,16 @@ - + - + @@ -1215,16 +1236,16 @@ - + - + @@ -1233,16 +1254,16 @@ - + - + @@ -1251,16 +1272,16 @@ - + - + @@ -1268,9 +1289,9 @@ - + @@ -7948,7 +7969,7 @@ + CornerRadius="5" Margin="-170,-140,-147,37"> + + + + + + /// 初始化墨迹渐隐管理器 + /// + private void InitializeInkFadeManager() + { + try + { + // 确保墨迹渐隐管理器已初始化 + if (_inkFadeManager == null) + { + _inkFadeManager = new InkFadeManager(this); + } + + // 同步设置状态 + _inkFadeManager.IsEnabled = Settings.Canvas.EnableInkFade; + _inkFadeManager.UpdateFadeTime(Settings.Canvas.InkFadeTime); + + LogHelper.WriteLogToFile("墨迹渐隐管理器已初始化", LogHelper.LogType.Event); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"初始化墨迹渐隐管理器时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + /// /// 初始化全局快捷键管理器 /// @@ -2006,5 +2047,74 @@ namespace Ink_Canvas } } #endregion + + #region 墨迹渐隐功能 + /// + /// 墨迹渐隐开关切换事件处理 + /// + private void ToggleSwitchEnableInkFade_Toggled(object sender, RoutedEventArgs e) + { + try + { + Settings.Canvas.EnableInkFade = ToggleSwitchEnableInkFade.IsOn; + _inkFadeManager.IsEnabled = Settings.Canvas.EnableInkFade; + + // 同步批注子面板中的开关状态 + if (ToggleSwitchInkFadeInPanel != null) + { + ToggleSwitchInkFadeInPanel.IsOn = Settings.Canvas.EnableInkFade; + } + + LogHelper.WriteLogToFile($"墨迹渐隐功能已{(Settings.Canvas.EnableInkFade ? "启用" : "禁用")}", LogHelper.LogType.Event); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"切换墨迹渐隐功能时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 墨迹渐隐时间滑块值改变事件处理 + /// + private void InkFadeTimeSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs e) + { + try + { + Settings.Canvas.InkFadeTime = (int)e.NewValue; + _inkFadeManager.UpdateFadeTime(Settings.Canvas.InkFadeTime); + LogHelper.WriteLogToFile($"墨迹渐隐时间已更新为 {Settings.Canvas.InkFadeTime}ms", LogHelper.LogType.Event); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"更新墨迹渐隐时间时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + + + + /// + /// 批注子面板中墨迹渐隐开关切换事件处理 + /// + private void ToggleSwitchInkFadeInPanel_Toggled(object sender, RoutedEventArgs e) + { + try + { + Settings.Canvas.EnableInkFade = ToggleSwitchInkFadeInPanel.IsOn; + _inkFadeManager.IsEnabled = Settings.Canvas.EnableInkFade; + + // 同步设置面板中的开关状态 + if (ToggleSwitchEnableInkFade != null) + { + ToggleSwitchEnableInkFade.IsOn = Settings.Canvas.EnableInkFade; + } + + LogHelper.WriteLogToFile($"批注子面板中墨迹渐隐功能已{(Settings.Canvas.EnableInkFade ? "启用" : "禁用")}", LogHelper.LogType.Event); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"批注子面板中切换墨迹渐隐功能时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + #endregion } } diff --git a/Ink Canvas/MainWindow_cs/MW_Settings.cs b/Ink Canvas/MainWindow_cs/MW_Settings.cs index 011c9842..03054a45 100644 --- a/Ink Canvas/MainWindow_cs/MW_Settings.cs +++ b/Ink Canvas/MainWindow_cs/MW_Settings.cs @@ -2405,74 +2405,146 @@ namespace Ink_Canvas #region 浮动栏按钮显示控制 - private void ToggleSwitchShowShapeButton_Toggled(object sender, RoutedEventArgs e) + private void CheckBoxShowShapeButton_Checked(object sender, RoutedEventArgs e) { if (!isLoaded) return; - Settings.Appearance.IsShowShapeButton = ToggleSwitchShowShapeButton.IsOn; + Settings.Appearance.IsShowShapeButton = CheckBoxShowShapeButton.IsChecked ?? false; UpdateFloatingBarButtonsVisibility(); SaveSettingsToFile(); } - private void ToggleSwitchShowUndoButton_Toggled(object sender, RoutedEventArgs e) + private void CheckBoxShowShapeButton_Unchecked(object sender, RoutedEventArgs e) { if (!isLoaded) return; - Settings.Appearance.IsShowUndoButton = ToggleSwitchShowUndoButton.IsOn; + Settings.Appearance.IsShowShapeButton = CheckBoxShowShapeButton.IsChecked ?? false; UpdateFloatingBarButtonsVisibility(); SaveSettingsToFile(); } - private void ToggleSwitchShowRedoButton_Toggled(object sender, RoutedEventArgs e) + private void CheckBoxShowUndoButton_Checked(object sender, RoutedEventArgs e) { if (!isLoaded) return; - Settings.Appearance.IsShowRedoButton = ToggleSwitchShowRedoButton.IsOn; + Settings.Appearance.IsShowUndoButton = CheckBoxShowUndoButton.IsChecked ?? false; UpdateFloatingBarButtonsVisibility(); SaveSettingsToFile(); } - private void ToggleSwitchShowClearButton_Toggled(object sender, RoutedEventArgs e) + private void CheckBoxShowUndoButton_Unchecked(object sender, RoutedEventArgs e) { if (!isLoaded) return; - Settings.Appearance.IsShowClearButton = ToggleSwitchShowClearButton.IsOn; + Settings.Appearance.IsShowUndoButton = CheckBoxShowUndoButton.IsChecked ?? false; UpdateFloatingBarButtonsVisibility(); SaveSettingsToFile(); } - private void ToggleSwitchShowWhiteboardButton_Toggled(object sender, RoutedEventArgs e) + private void CheckBoxShowRedoButton_Checked(object sender, RoutedEventArgs e) { if (!isLoaded) return; - Settings.Appearance.IsShowWhiteboardButton = ToggleSwitchShowWhiteboardButton.IsOn; + Settings.Appearance.IsShowRedoButton = CheckBoxShowRedoButton.IsChecked ?? false; + UpdateFloatingBarButtonsVisibility(); + SaveSettingsToFile(); + } + + private void CheckBoxShowRedoButton_Unchecked(object sender, RoutedEventArgs e) + { + if (!isLoaded) return; + Settings.Appearance.IsShowRedoButton = CheckBoxShowRedoButton.IsChecked ?? false; + UpdateFloatingBarButtonsVisibility(); + SaveSettingsToFile(); + } + + private void CheckBoxShowClearButton_Checked(object sender, RoutedEventArgs e) + { + if (!isLoaded) return; + Settings.Appearance.IsShowClearButton = CheckBoxShowClearButton.IsChecked ?? false; + UpdateFloatingBarButtonsVisibility(); + SaveSettingsToFile(); + } + + private void CheckBoxShowClearButton_Unchecked(object sender, RoutedEventArgs e) + { + if (!isLoaded) return; + Settings.Appearance.IsShowClearButton = CheckBoxShowClearButton.IsChecked ?? false; + UpdateFloatingBarButtonsVisibility(); + SaveSettingsToFile(); + } + + private void CheckBoxShowWhiteboardButton_Checked(object sender, RoutedEventArgs e) + { + if (!isLoaded) return; + Settings.Appearance.IsShowWhiteboardButton = CheckBoxShowWhiteboardButton.IsChecked ?? false; + UpdateFloatingBarButtonsVisibility(); + SaveSettingsToFile(); + } + + private void CheckBoxShowWhiteboardButton_Unchecked(object sender, RoutedEventArgs e) + { + if (!isLoaded) return; + Settings.Appearance.IsShowWhiteboardButton = CheckBoxShowWhiteboardButton.IsChecked ?? false; UpdateFloatingBarButtonsVisibility(); SaveSettingsToFile(); } - private void ToggleSwitchShowLassoSelectButton_Toggled(object sender, RoutedEventArgs e) + private void CheckBoxShowLassoSelectButton_Checked(object sender, RoutedEventArgs e) { if (!isLoaded) return; - Settings.Appearance.IsShowLassoSelectButton = ToggleSwitchShowLassoSelectButton.IsOn; + Settings.Appearance.IsShowLassoSelectButton = CheckBoxShowLassoSelectButton.IsChecked ?? false; UpdateFloatingBarButtonsVisibility(); SaveSettingsToFile(); } - private void ToggleSwitchShowClearAndMouseButton_Toggled(object sender, RoutedEventArgs e) + private void CheckBoxShowLassoSelectButton_Unchecked(object sender, RoutedEventArgs e) { if (!isLoaded) return; - Settings.Appearance.IsShowClearAndMouseButton = ToggleSwitchShowClearAndMouseButton.IsOn; + Settings.Appearance.IsShowLassoSelectButton = CheckBoxShowLassoSelectButton.IsChecked ?? false; UpdateFloatingBarButtonsVisibility(); SaveSettingsToFile(); } - private void ToggleSwitchShowHideButton_Toggled(object sender, RoutedEventArgs e) + private void CheckBoxShowClearAndMouseButton_Checked(object sender, RoutedEventArgs e) { if (!isLoaded) return; - Settings.Appearance.IsShowHideButton = ToggleSwitchShowHideButton.IsOn; + Settings.Appearance.IsShowClearAndMouseButton = CheckBoxShowClearAndMouseButton.IsChecked ?? false; UpdateFloatingBarButtonsVisibility(); SaveSettingsToFile(); } - private void ToggleSwitchShowQuickColorPalette_Toggled(object sender, RoutedEventArgs e) + private void CheckBoxShowClearAndMouseButton_Unchecked(object sender, RoutedEventArgs e) { if (!isLoaded) return; - Settings.Appearance.IsShowQuickColorPalette = ToggleSwitchShowQuickColorPalette.IsOn; + Settings.Appearance.IsShowClearAndMouseButton = CheckBoxShowClearAndMouseButton.IsChecked ?? false; + UpdateFloatingBarButtonsVisibility(); + SaveSettingsToFile(); + } + + private void CheckBoxShowHideButton_Checked(object sender, RoutedEventArgs e) + { + if (!isLoaded) return; + Settings.Appearance.IsShowHideButton = CheckBoxShowHideButton.IsChecked ?? false; + UpdateFloatingBarButtonsVisibility(); + SaveSettingsToFile(); + } + + private void CheckBoxShowHideButton_Unchecked(object sender, RoutedEventArgs e) + { + if (!isLoaded) return; + Settings.Appearance.IsShowHideButton = CheckBoxShowHideButton.IsChecked ?? false; + UpdateFloatingBarButtonsVisibility(); + SaveSettingsToFile(); + } + + private void CheckBoxShowQuickColorPalette_Checked(object sender, RoutedEventArgs e) + { + if (!isLoaded) return; + Settings.Appearance.IsShowQuickColorPalette = CheckBoxShowQuickColorPalette.IsChecked ?? false; + UpdateFloatingBarButtonsVisibility(); + SaveSettingsToFile(); + } + + private void CheckBoxShowQuickColorPalette_Unchecked(object sender, RoutedEventArgs e) + { + if (!isLoaded) return; + Settings.Appearance.IsShowQuickColorPalette = CheckBoxShowQuickColorPalette.IsChecked ?? false; UpdateFloatingBarButtonsVisibility(); SaveSettingsToFile(); } diff --git a/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs b/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs index 454f45a1..dfb93232 100644 --- a/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs +++ b/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs @@ -307,15 +307,15 @@ namespace Ink_Canvas Settings.Appearance.EnableChickenSoupInWhiteboardMode; // 浮动栏按钮显示控制开关初始化 - ToggleSwitchShowShapeButton.IsOn = Settings.Appearance.IsShowShapeButton; - ToggleSwitchShowUndoButton.IsOn = Settings.Appearance.IsShowUndoButton; - ToggleSwitchShowRedoButton.IsOn = Settings.Appearance.IsShowRedoButton; - ToggleSwitchShowClearButton.IsOn = Settings.Appearance.IsShowClearButton; - ToggleSwitchShowWhiteboardButton.IsOn = Settings.Appearance.IsShowWhiteboardButton; - ToggleSwitchShowHideButton.IsOn = Settings.Appearance.IsShowHideButton; - ToggleSwitchShowQuickColorPalette.IsOn = Settings.Appearance.IsShowQuickColorPalette; - ToggleSwitchShowLassoSelectButton.IsOn = Settings.Appearance.IsShowLassoSelectButton; - ToggleSwitchShowClearAndMouseButton.IsOn = Settings.Appearance.IsShowClearAndMouseButton; + CheckBoxShowShapeButton.IsChecked = Settings.Appearance.IsShowShapeButton; + CheckBoxShowUndoButton.IsChecked = Settings.Appearance.IsShowUndoButton; + CheckBoxShowRedoButton.IsChecked = Settings.Appearance.IsShowRedoButton; + CheckBoxShowClearButton.IsChecked = Settings.Appearance.IsShowClearButton; + CheckBoxShowWhiteboardButton.IsChecked = Settings.Appearance.IsShowWhiteboardButton; + CheckBoxShowHideButton.IsChecked = Settings.Appearance.IsShowHideButton; + CheckBoxShowQuickColorPalette.IsChecked = Settings.Appearance.IsShowQuickColorPalette; + CheckBoxShowLassoSelectButton.IsChecked = Settings.Appearance.IsShowLassoSelectButton; + CheckBoxShowClearAndMouseButton.IsChecked = Settings.Appearance.IsShowClearAndMouseButton; ComboBoxEraserDisplayOption.SelectedIndex = Settings.Appearance.EraserDisplayOption; ComboBoxQuickColorPaletteDisplayMode.SelectedIndex = Settings.Appearance.QuickColorPaletteDisplayMode; @@ -883,6 +883,51 @@ namespace Ink_Canvas { ViewboxFloatingBarMarginAnimation(100, true); } + + // 加载墨迹渐隐设置 + LoadInkFadeSettings(); + } + + /// + /// 加载墨迹渐隐设置 + /// + private void LoadInkFadeSettings() + { + try + { + // 同步设置面板中的开关状态 + if (ToggleSwitchEnableInkFade != null) + { + ToggleSwitchEnableInkFade.IsOn = Settings.Canvas.EnableInkFade; + } + + // 同步批注子面板中的开关状态 + if (ToggleSwitchInkFadeInPanel != null) + { + ToggleSwitchInkFadeInPanel.IsOn = Settings.Canvas.EnableInkFade; + } + + // 同步滑块值 + if (InkFadeTimeSlider != null) + { + InkFadeTimeSlider.Value = Settings.Canvas.InkFadeTime; + } + + + + // 同步墨迹渐隐管理器的状态 + if (_inkFadeManager != null) + { + _inkFadeManager.IsEnabled = Settings.Canvas.EnableInkFade; + _inkFadeManager.UpdateFadeTime(Settings.Canvas.InkFadeTime); + } + + LogHelper.WriteLogToFile("墨迹渐隐设置已加载", LogHelper.LogType.Event); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"加载墨迹渐隐设置时出错: {ex.Message}", LogHelper.LogType.Error); + } } } } \ No newline at end of file diff --git a/Ink Canvas/MainWindow_cs/MW_SimulatePressure&InkToShape.cs b/Ink Canvas/MainWindow_cs/MW_SimulatePressure&InkToShape.cs index 4dae95ad..4a6c108f 100644 --- a/Ink Canvas/MainWindow_cs/MW_SimulatePressure&InkToShape.cs +++ b/Ink Canvas/MainWindow_cs/MW_SimulatePressure&InkToShape.cs @@ -56,6 +56,36 @@ namespace Ink_Canvas private void inkCanvas_StrokeCollected(object sender, InkCanvasStrokeCollectedEventArgs e) { + // 检查是否启用墨迹渐隐功能 + if (Settings.Canvas.EnableInkFade) + { + LogHelper.WriteLogToFile("StrokeCollected: 进入墨迹渐隐模式", LogHelper.LogType.Info); + + // 获取墨迹的起点和终点 + var startPoint = e.Stroke.StylusPoints.Count > 0 ? e.Stroke.StylusPoints[0].ToPoint() : new Point(); + var endPoint = e.Stroke.StylusPoints.Count > 0 ? e.Stroke.StylusPoints[e.Stroke.StylusPoints.Count - 1].ToPoint() : new Point(); + + // 从InkCanvas中移除墨迹,因为我们要用渐隐管理器来管理它 + if (inkCanvas.Strokes.Contains(e.Stroke)) + { + inkCanvas.Strokes.Remove(e.Stroke); + } + + // 添加到墨迹渐隐管理器 + if (_inkFadeManager != null) + { + _inkFadeManager.AddFadingStroke(e.Stroke, startPoint, endPoint); + LogHelper.WriteLogToFile($"StrokeCollected: 墨迹已添加到渐隐管理器,起点:{startPoint},终点:{endPoint}", LogHelper.LogType.Info); + } + else + { + LogHelper.WriteLogToFile("StrokeCollected: 墨迹渐隐管理器为空,无法添加墨迹", LogHelper.LogType.Error); + } + + // 墨迹渐隐模式下不参与墨迹纠正和其他处理,直接返回 + return; + } + // 标记是否进行了直线拉直 bool wasStraightened = false; diff --git a/Ink Canvas/MainWindow_cs/MW_TouchEvents.cs b/Ink Canvas/MainWindow_cs/MW_TouchEvents.cs index 44a9671d..0c3bbf74 100644 --- a/Ink Canvas/MainWindow_cs/MW_TouchEvents.cs +++ b/Ink Canvas/MainWindow_cs/MW_TouchEvents.cs @@ -139,10 +139,13 @@ namespace Ink_Canvas private void MainWindow_StylusDown(object sender, StylusDownEventArgs e) { + LogHelper.WriteLogToFile($"MainWindow_StylusDown 被调用,笔尾状态: {e.StylusDevice.Inverted}, 当前 drawingShapeMode: {drawingShapeMode}, 当前 EditingMode: {inkCanvas.EditingMode}", LogHelper.LogType.Info); + // 新增:根据是否为笔尾自动切换橡皮擦/画笔模式 if (e.StylusDevice.Inverted) { inkCanvas.EditingMode = InkCanvasEditingMode.EraseByPoint; + LogHelper.WriteLogToFile("检测到笔尾,设置 EditingMode 为 EraseByPoint", LogHelper.LogType.Info); } else { @@ -151,12 +154,18 @@ namespace Ink_Canvas { // 确保几何绘制模式下不切换到Ink模式,避免触摸轨迹被收集 inkCanvas.EditingMode = InkCanvasEditingMode.None; + LogHelper.WriteLogToFile("几何绘制模式,设置 EditingMode 为 None", LogHelper.LogType.Info); return; } // 修复:保持当前的线擦模式,不要强制切换到Ink模式 if (inkCanvas.EditingMode != InkCanvasEditingMode.EraseByStroke) { inkCanvas.EditingMode = InkCanvasEditingMode.Ink; + LogHelper.WriteLogToFile("设置 EditingMode 为 Ink", LogHelper.LogType.Info); + } + else + { + LogHelper.WriteLogToFile("保持当前线擦模式", LogHelper.LogType.Info); } } SetCursorBasedOnEditingMode(inkCanvas); @@ -198,15 +207,25 @@ namespace Ink_Canvas { try { - inkCanvas.Strokes.Add(GetStrokeVisual(e.StylusDevice.Id).Stroke); + LogHelper.WriteLogToFile($"MainWindow_StylusUp 被调用,EditingMode: {inkCanvas.EditingMode}, EnableInkFade: {Settings.Canvas.EnableInkFade}", LogHelper.LogType.Info); + + var stroke = GetStrokeVisual(e.StylusDevice.Id).Stroke; + LogHelper.WriteLogToFile($"获取到墨迹,StylusPoints数量: {stroke.StylusPoints.Count}", LogHelper.LogType.Info); + + // 正常模式:添加到画布并参与墨迹纠正 + // 墨迹渐隐功能现在在 StrokeCollected 事件中统一处理所有输入方式 + LogHelper.WriteLogToFile("StylusUp: 添加墨迹到画布", LogHelper.LogType.Info); + + inkCanvas.Strokes.Add(stroke); await Task.Delay(5); // 避免渲染墨迹完成前预览墨迹被删除导致墨迹闪烁 inkCanvas.Children.Remove(GetVisualCanvas(e.StylusDevice.Id)); inkCanvas_StrokeCollected(inkCanvas, - new InkCanvasStrokeCollectedEventArgs(GetStrokeVisual(e.StylusDevice.Id).Stroke)); + new InkCanvasStrokeCollectedEventArgs(stroke)); } catch (Exception ex) { + LogHelper.WriteLogToFile($"MainWindow_StylusUp 出错: {ex}", LogHelper.LogType.Error); Label.Content = ex.ToString(); } diff --git a/Ink Canvas/Resources/Settings.cs b/Ink Canvas/Resources/Settings.cs index a80971c5..b617f46d 100644 --- a/Ink Canvas/Resources/Settings.cs +++ b/Ink Canvas/Resources/Settings.cs @@ -92,6 +92,13 @@ namespace Ink_Canvas public bool ClearCanvasAlsoClearImages { get; set; } = true; [JsonProperty("showCircleCenter")] public bool ShowCircleCenter { get; set; } = false; + + // 墨迹渐隐功能设置 + [JsonProperty("enableInkFade")] + public bool EnableInkFade { get; set; } = false; // 是否启用墨迹渐隐功能 + [JsonProperty("inkFadeTime")] + public int InkFadeTime { get; set; } = 3000; // 墨迹渐隐时间(毫秒) + } public enum OptionalOperation