This commit is contained in:
2025-08-23 23:13:39 +08:00
parent 710a9014dd
commit 5665fcc823
8 changed files with 1019 additions and 93 deletions
+647 -36
View File
@@ -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<Stroke, DispatcherTimer> _fadeTimers;
private readonly Dictionary<Stroke, UIElement> _strokeVisuals;
private readonly Dictionary<Stroke, Point> _strokeStartPoints;
private readonly Dictionary<Stroke, Point> _strokeEndPoints;
#endregion
#region Constructor
@@ -43,6 +50,8 @@ namespace Ink_Canvas.Helpers
_dispatcher = _mainWindow.Dispatcher;
_fadeTimers = new Dictionary<Stroke, DispatcherTimer>();
_strokeVisuals = new Dictionary<Stroke, UIElement>();
_strokeStartPoints = new Dictionary<Stroke, Point>();
_strokeEndPoints = new Dictionary<Stroke, Point>();
}
#endregion
@@ -51,15 +60,31 @@ namespace Ink_Canvas.Helpers
/// 添加需要渐隐的墨迹
/// </summary>
/// <param name="stroke">墨迹对象</param>
/// <param name="visual">墨迹的视觉元素</param>
public void AddFadingStroke(Stroke stroke, UIElement visual)
/// <param name="startPoint">落笔点</param>
/// <param name="endPoint">抬笔点</param>
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
}
}
/// <summary>
/// 更新动画持续时间设置
/// 启用墨迹渐隐功能
/// </summary>
/// <param name="animationDuration">新的动画持续时间(毫秒)</param>
public void UpdateAnimationDuration(int animationDuration)
public void Enable()
{
AnimationDuration = animationDuration;
IsEnabled = true;
LogHelper.WriteLogToFile("墨迹渐隐功能已启用", LogHelper.LogType.Info);
}
/// <summary>
/// 禁用墨迹渐隐功能
/// </summary>
public void Disable()
{
IsEnabled = false;
LogHelper.WriteLogToFile("墨迹渐隐功能已禁用", LogHelper.LogType.Info);
}
#endregion
#region Private Methods
/// <summary>
/// 创建墨迹的视觉元素
/// </summary>
/// <param name="stroke">墨迹对象</param>
/// <returns>视觉元素</returns>
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;
}
}
/// <summary>
/// 开始渐隐动画
/// </summary>
@@ -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);
}
}
/// <summary>
/// 开始普通墨迹的渐隐动画
/// </summary>
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);
}
}
/// <summary>
/// 开始高亮笔的渐隐动画
/// </summary>
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);
}
}
/// <summary>
/// 渐进式渐隐动画 - 从起点到终点逐渐消失
/// </summary>
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);
}
}
/// <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);
LogHelper.WriteLogToFile($"创建 {segmentCount} 个分段,每段约 {pointsPerSegment} 个点", LogHelper.LogType.Info);
// 隐藏原始视觉元素
originalVisual.Visibility = Visibility.Hidden;
var segments = new List<UIElement>();
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);
}
}
/// <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 ex)
{
LogHelper.WriteLogToFile($"创建墨迹分段失败: {ex}", LogHelper.LogType.Error);
return null;
}
}
/// <summary>
/// 开始分段渐隐动画
/// </summary>
private void StartSegmentedFadeAnimation(List<UIElement> 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<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);
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);
}
}
/// <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
{
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);
}
}
/// <summary>
/// 简单渐隐动画(备用方案)
/// </summary>
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);
}
}
/// <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
{
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
}
}
+60 -28
View File
@@ -832,6 +832,27 @@
IsOn="True" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Toggled="ToggleSwitchAdvancedBezierSmoothing_Toggled" />
</ui:SimpleStackPanel>
<Line HorizontalAlignment="Center" X1="0" Y1="0" X2="400" Y2="0" Stroke="#3f3f46"
StrokeThickness="1" Margin="0,4,0,4" />
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
<TextBlock Foreground="#fafafa" Text="启用墨迹渐隐功能" VerticalAlignment="Center"
FontSize="14" Margin="0,0,16,0" />
<ui:ToggleSwitch OnContent="" OffContent="" Name="ToggleSwitchEnableInkFade"
IsOn="False" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Toggled="ToggleSwitchEnableInkFade_Toggled" />
</ui:SimpleStackPanel>
<TextBlock Text="# 开启后墨迹不会绘制到画布上,而是保持湿墨迹状态,根据设置的渐隐时间自动消失" TextWrapping="Wrap" Foreground="#a1a1aa" />
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left"
Visibility="{Binding ElementName=ToggleSwitchEnableInkFade, Path=IsOn, Converter={StaticResource BooleanToVisibilityConverter}}">
<TextBlock Foreground="#fafafa" Text="墨迹渐隐时间" VerticalAlignment="Center"
FontSize="14" Margin="0,0,16,0" />
<Slider Name="InkFadeTimeSlider" Width="150" Minimum="1000" Maximum="10000"
Value="3000" TickFrequency="1000" IsSnapToTickEnabled="True"
ValueChanged="InkFadeTimeSlider_ValueChanged" />
<TextBlock Foreground="#fafafa" Text="{Binding ElementName=InkFadeTimeSlider, Path=Value, StringFormat={}{0:0}ms}"
VerticalAlignment="Center" FontSize="14" Margin="16,0,0,0" />
</ui:SimpleStackPanel>
</ui:SimpleStackPanel>
</GroupBox>
<!-- 新增:崩溃后操作设置 -->
@@ -1197,16 +1218,16 @@
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left" Width="190">
<TextBlock Foreground="#fafafa" Text="显示形状按钮" VerticalAlignment="Center"
FontSize="14" Margin="0,0,8,0" />
<ui:ToggleSwitch OnContent="" OffContent="" Name="ToggleSwitchShowShapeButton"
IsOn="True" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Toggled="ToggleSwitchShowShapeButton_Toggled" />
<CheckBox Name="CheckBoxShowShapeButton"
IsChecked="True" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Checked="CheckBoxShowShapeButton_Checked" Unchecked="CheckBoxShowShapeButton_Unchecked" />
</ui:SimpleStackPanel>
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left" Width="190">
<TextBlock Foreground="#fafafa" Text="显示撤销按钮" VerticalAlignment="Center"
FontSize="14" Margin="0,0,8,0" />
<ui:ToggleSwitch OnContent="" OffContent="" Name="ToggleSwitchShowUndoButton"
IsOn="True" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Toggled="ToggleSwitchShowUndoButton_Toggled" />
<CheckBox Name="CheckBoxShowUndoButton"
IsChecked="True" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Checked="CheckBoxShowUndoButton_Checked" Unchecked="CheckBoxShowUndoButton_Unchecked" />
</ui:SimpleStackPanel>
</ui:SimpleStackPanel>
@@ -1215,16 +1236,16 @@
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left" Width="190">
<TextBlock Foreground="#fafafa" Text="显示重做按钮" VerticalAlignment="Center"
FontSize="14" Margin="0,0,8,0" />
<ui:ToggleSwitch OnContent="" OffContent="" Name="ToggleSwitchShowRedoButton"
IsOn="True" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Toggled="ToggleSwitchShowRedoButton_Toggled" />
<CheckBox Name="CheckBoxShowRedoButton"
IsChecked="True" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Checked="CheckBoxShowRedoButton_Checked" Unchecked="CheckBoxShowRedoButton_Unchecked" />
</ui:SimpleStackPanel>
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left" Width="190">
<TextBlock Foreground="#fafafa" Text="显示清空按钮" VerticalAlignment="Center"
FontSize="14" Margin="0,0,8,0" />
<ui:ToggleSwitch OnContent="" OffContent="" Name="ToggleSwitchShowClearButton"
IsOn="True" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Toggled="ToggleSwitchShowClearButton_Toggled" />
<CheckBox Name="CheckBoxShowClearButton"
IsChecked="True" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Checked="CheckBoxShowClearButton_Checked" Unchecked="CheckBoxShowClearButton_Unchecked" />
</ui:SimpleStackPanel>
</ui:SimpleStackPanel>
@@ -1233,16 +1254,16 @@
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left" Width="190">
<TextBlock Foreground="#fafafa" Text="显示白板按钮" VerticalAlignment="Center"
FontSize="14" Margin="0,0,8,0" />
<ui:ToggleSwitch OnContent="" OffContent="" Name="ToggleSwitchShowWhiteboardButton"
IsOn="True" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Toggled="ToggleSwitchShowWhiteboardButton_Toggled" />
<CheckBox Name="CheckBoxShowWhiteboardButton"
IsChecked="True" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Checked="CheckBoxShowWhiteboardButton_Checked" Unchecked="CheckBoxShowWhiteboardButton_Unchecked" />
</ui:SimpleStackPanel>
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left" Width="190">
<TextBlock Foreground="#fafafa" Text="显示隐藏按钮" VerticalAlignment="Center"
FontSize="14" Margin="0,0,8,0" />
<ui:ToggleSwitch OnContent="" OffContent="" Name="ToggleSwitchShowHideButton"
IsOn="True" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Toggled="ToggleSwitchShowHideButton_Toggled" />
<CheckBox Name="CheckBoxShowHideButton"
IsChecked="True" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Checked="CheckBoxShowHideButton_Checked" Unchecked="CheckBoxShowHideButton_Unchecked" />
</ui:SimpleStackPanel>
</ui:SimpleStackPanel>
@@ -1251,16 +1272,16 @@
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left" Width="190">
<TextBlock Foreground="#fafafa" Text="显示套索选择按钮" VerticalAlignment="Center"
FontSize="14" Margin="0,0,8,0" />
<ui:ToggleSwitch OnContent="" OffContent="" Name="ToggleSwitchShowLassoSelectButton"
IsOn="True" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Toggled="ToggleSwitchShowLassoSelectButton_Toggled" />
<CheckBox Name="CheckBoxShowLassoSelectButton"
IsChecked="True" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Checked="CheckBoxShowLassoSelectButton_Checked" Unchecked="CheckBoxShowLassoSelectButton_Unchecked" />
</ui:SimpleStackPanel>
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left" Width="190">
<TextBlock Foreground="#fafafa" Text="显示清并鼠按钮" VerticalAlignment="Center"
FontSize="14" Margin="0,0,8,0" />
<ui:ToggleSwitch OnContent="" OffContent="" Name="ToggleSwitchShowClearAndMouseButton"
IsOn="True" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Toggled="ToggleSwitchShowClearAndMouseButton_Toggled" />
<CheckBox Name="CheckBoxShowClearAndMouseButton"
IsChecked="True" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Checked="CheckBoxShowClearAndMouseButton_Checked" Unchecked="CheckBoxShowClearAndMouseButton_Unchecked" />
</ui:SimpleStackPanel>
</ui:SimpleStackPanel>
@@ -1268,9 +1289,9 @@
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="0,0,0,8">
<TextBlock Foreground="#fafafa" Text="显示快捷调色盘" VerticalAlignment="Center"
FontSize="14" Margin="0,0,8,0" />
<ui:ToggleSwitch OnContent="" OffContent="" Name="ToggleSwitchShowQuickColorPalette"
IsOn="False" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Toggled="ToggleSwitchShowQuickColorPalette_Toggled" />
<CheckBox Name="CheckBoxShowQuickColorPalette"
IsChecked="False" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
Checked="CheckBoxShowQuickColorPalette_Checked" Unchecked="CheckBoxShowQuickColorPalette_Unchecked" />
</ui:SimpleStackPanel>
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
@@ -7948,7 +7969,7 @@
<Border x:Name="BorderDrawShape" Background="#fafafa" Opacity="1"
Visibility="Visible" BorderBrush="#2563eb"
BorderThickness="1"
CornerRadius="5" Margin="-170,-122,-147,37">
CornerRadius="5" Margin="-170,-140,-147,37">
<ui:SimpleStackPanel Spacing="0" Orientation="Vertical">
<!--<TextBlock FontSize="16" Foreground="{DynamicResource FloatBarForeground}" HorizontalAlignment="Left" Margin="10,8">
<Run Text="图形"/>
@@ -7973,6 +7994,17 @@
</Canvas>
</ui:SimpleStackPanel>
</Border>
<!-- 墨迹渐隐开关 -->
<ui:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left"
Margin="8,8,8,4" Spacing="8">
<TextBlock Foreground="#1e3a8a" Text="墨迹渐隐" FontSize="12"
VerticalAlignment="Center" FontWeight="SemiBold" />
<ui:ToggleSwitch OnContent="" OffContent="" Name="ToggleSwitchInkFadeInPanel"
IsOn="False" FontFamily="Microsoft YaHei UI"
FontWeight="Bold" Toggled="ToggleSwitchInkFadeInPanel_Toggled" />
</ui:SimpleStackPanel>
<Line HorizontalAlignment="Center" X1="0" Y1="0" X2="315" Y2="0"
Stroke="#e5e7eb" StrokeThickness="1" Margin="8,0,8,4" />
<ui:SimpleStackPanel Orientation="Horizontal" Height="38" Spacing="2"
Margin="4,6,4,0">
<ui:SimpleStackPanel Name="BoardImageDrawLine"
+110
View File
@@ -45,6 +45,9 @@ namespace Ink_Canvas
// 全局快捷键管理器
private GlobalHotkeyManager _globalHotkeyManager;
// 墨迹渐隐管理器
private InkFadeManager _inkFadeManager;
#region Window Initialization
@@ -155,6 +158,9 @@ namespace Ink_Canvas
// 初始化墨迹平滑管理器
_inkSmoothingManager = new InkSmoothingManager(Dispatcher);
// 初始化墨迹渐隐管理器
_inkFadeManager = new InkFadeManager(this);
// 注册输入事件
inkCanvas.PreviewMouseDown += inkCanvas_PreviewMouseDown;
inkCanvas.StylusDown += inkCanvas_StylusDown;
@@ -501,6 +507,9 @@ namespace Ink_Canvas
// 初始化全局快捷键管理器
InitializeGlobalHotkeyManager();
// 初始化墨迹渐隐管理器
InitializeInkFadeManager();
}
private void SystemEventsOnDisplaySettingsChanged(object sender, EventArgs e)
@@ -637,6 +646,13 @@ namespace Ink_Canvas
_globalHotkeyManager = null;
}
// 清理墨迹渐隐管理器
if (_inkFadeManager != null)
{
_inkFadeManager.ClearAllFadingStrokes();
_inkFadeManager = null;
}
// 停止置顶维护定时器
StopTopmostMaintenance();
@@ -1967,6 +1983,31 @@ namespace Ink_Canvas
#endregion
#region
/// <summary>
/// 初始化墨迹渐隐管理器
/// </summary>
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);
}
}
/// <summary>
/// 初始化全局快捷键管理器
/// </summary>
@@ -2006,5 +2047,74 @@ namespace Ink_Canvas
}
}
#endregion
#region
/// <summary>
/// 墨迹渐隐开关切换事件处理
/// </summary>
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);
}
}
/// <summary>
/// 墨迹渐隐时间滑块值改变事件处理
/// </summary>
private void InkFadeTimeSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> 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);
}
}
/// <summary>
/// 批注子面板中墨迹渐隐开关切换事件处理
/// </summary>
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
}
}
+90 -18
View File
@@ -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();
}
+54 -9
View File
@@ -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();
}
/// <summary>
/// 加载墨迹渐隐设置
/// </summary>
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);
}
}
}
}
@@ -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;
+21 -2
View File
@@ -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();
}
+7
View File
@@ -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