diff --git a/Ink Canvas/MainWindow_cs/MW_Settings.cs b/Ink Canvas/MainWindow_cs/MW_Settings.cs index 4851f7c4..41a6726a 100644 --- a/Ink Canvas/MainWindow_cs/MW_Settings.cs +++ b/Ink Canvas/MainWindow_cs/MW_Settings.cs @@ -915,6 +915,7 @@ namespace Ink_Canvas Settings.Canvas.DisablePressure = false; Settings.Canvas.AutoStraightenLine = true; Settings.Canvas.AutoStraightenLineThreshold = 80; + Settings.Canvas.PauseStraightenLine = false; Settings.Canvas.LineEndpointSnapping = true; Settings.Canvas.LineEndpointSnappingThreshold = 15; Settings.Canvas.UsingWhiteboard = false; diff --git a/Ink Canvas/MainWindow_cs/MW_ShapeDrawing.cs b/Ink Canvas/MainWindow_cs/MW_ShapeDrawing.cs index c3610b19..fcb2e13a 100644 --- a/Ink Canvas/MainWindow_cs/MW_ShapeDrawing.cs +++ b/Ink Canvas/MainWindow_cs/MW_ShapeDrawing.cs @@ -2517,6 +2517,7 @@ namespace Ink_Canvas _isMouseRealtimeInking = true; inkCanvas.EditingMode = InkCanvasEditingMode.None; var p = e.GetPosition(inkCanvas); + CancelPauseStraightenTimer(MouseRealtimeStrokeId); InitializeRealtimeBrushTipStateFromPoint(MouseRealtimeStrokeId, p); var sv = GetStrokeVisual(MouseRealtimeStrokeId); TryAppendRealtimeVelocityBrushTipPoint(sv, MouseRealtimeStrokeId, p); @@ -2547,7 +2548,10 @@ namespace Ink_Canvas { var sv = GetStrokeVisual(MouseRealtimeStrokeId); if (TryAppendRealtimeVelocityBrushTipPoint(sv, MouseRealtimeStrokeId, e.GetPosition(inkCanvas))) + { sv.ForceRedraw(); + ResetPauseStraightenTimer(MouseRealtimeStrokeId); + } else { _isMouseRealtimeInking = false; @@ -2612,6 +2616,7 @@ namespace Ink_Canvas VisualCanvasList.Remove(MouseRealtimeStrokeId); TouchDownPointsList.Remove(MouseRealtimeStrokeId); CleanupRealtimeBrushTipState(MouseRealtimeStrokeId); + CancelPauseStraightenTimer(MouseRealtimeStrokeId); _isMouseRealtimeInking = false; } } diff --git a/Ink Canvas/MainWindow_cs/MW_TouchEvents.cs b/Ink Canvas/MainWindow_cs/MW_TouchEvents.cs index ba37a2fb..66a73fc1 100644 --- a/Ink Canvas/MainWindow_cs/MW_TouchEvents.cs +++ b/Ink Canvas/MainWindow_cs/MW_TouchEvents.cs @@ -1,6 +1,7 @@ using Ink_Canvas.Helpers; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using System.Windows; @@ -8,6 +9,7 @@ using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; +using System.Windows.Threading; using Point = System.Windows.Point; namespace Ink_Canvas @@ -54,6 +56,9 @@ namespace Ink_Canvas private readonly HashSet _activeRealtimeTouchStrokeIds = new HashSet(); private readonly HashSet _activeTouchStrokeIds = new HashSet(); + private readonly Dictionary _pauseStraightenTimers = new Dictionary(); + private const int PauseStraightenDelayMs = 300; + private sealed class OneEuroFilter { private readonly float _minCutoff; @@ -762,6 +767,9 @@ namespace Ink_Canvas || inkCanvas.EditingMode == InkCanvasEditingMode.Select) return; InitializeRealtimeBrushTipState(e.StylusDevice.Id, e); + CancelPauseStraightenTimer(e.StylusDevice.Id); + _pauseStraightenInkModeStartPos = e.GetPosition(inkCanvas); + _pauseStraightenInkModeTracking = true; TouchDownPointsList[e.StylusDevice.Id] = InkCanvasEditingMode.None; } @@ -865,6 +873,9 @@ namespace Ink_Canvas VisualCanvasList.Remove(e.StylusDevice.Id); TouchDownPointsList.Remove(e.StylusDevice.Id); CleanupRealtimeBrushTipState(e.StylusDevice.Id); + CancelPauseStraightenTimer(e.StylusDevice.Id); + CancelPauseStraightenTimer(-200001); + _pauseStraightenInkModeTracking = false; if (StrokeVisualList.Count == 0 || VisualCanvasList.Count == 0 || TouchDownPointsList.Count == 0) { // 只清除手写笔预览相关的Canvas,不清除所有子元素 @@ -921,7 +932,14 @@ namespace Ink_Canvas return; } - if (GetTouchDownPointsList(e.StylusDevice.Id) != InkCanvasEditingMode.None) return; + if (GetTouchDownPointsList(e.StylusDevice.Id) != InkCanvasEditingMode.None) + { + // Regular Ink mode — InkCanvas builds the stroke internally. + // Track position for pause-straighten. + if (inkCanvas.EditingMode == InkCanvasEditingMode.Ink && drawingShapeMode == 0) + ResetPauseStraightenTimerInkMode(e.GetPosition(inkCanvas)); + return; + } try { if (e.StylusDevice.StylusButtons[1].StylusButtonState == StylusButtonState.Down) return; @@ -938,6 +956,8 @@ namespace Ink_Canvas strokeVisual.Add(new StylusPoint(stylusPoint.X, stylusPoint.Y, stylusPoint.PressureFactor)); } + ResetPauseStraightenTimer(e.StylusDevice.Id); + if (isHandledByRealtime) strokeVisual.ForceRedraw(); else @@ -987,6 +1007,124 @@ namespace Ink_Canvas return VisualCanvasList.TryGetValue(id, out var visualCanvas) ? visualCanvas : null; } + private void ResetPauseStraightenTimer(int stylusId) + { + if (!Settings.Canvas.PauseStraightenLine) return; + Debug.WriteLine($"ResetPauseStraightenTimer: id={stylusId}"); + if (_pauseStraightenTimers.TryGetValue(stylusId, out var existing)) + { + existing.Stop(); + existing.Start(); + return; + } + var timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(PauseStraightenDelayMs) }; + var capturedId = stylusId; + timer.Tick += (s, e) => + { + timer.Stop(); + _pauseStraightenTimers.Remove(capturedId); + Debug.WriteLine($"PauseStraightenTimer fired: id={capturedId}"); + TryPauseStraighten(capturedId); + }; + _pauseStraightenTimers[stylusId] = timer; + timer.Start(); + } + + private void ResetPauseStraightenTimerInkMode(Point currentPos) + { + if (!Settings.Canvas.PauseStraightenLine) return; + const int inkModeId = -200001; + _pauseStraightenInkModeLastPos = currentPos; + if (_pauseStraightenTimers.TryGetValue(inkModeId, out var existing)) + { + existing.Stop(); + existing.Start(); + return; + } + var timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(PauseStraightenDelayMs) }; + timer.Tick += (s, e) => + { + timer.Stop(); + _pauseStraightenTimers.Remove(inkModeId); + TryPauseStraightenInkMode(); + }; + _pauseStraightenTimers[inkModeId] = timer; + timer.Start(); + } + + private Point _pauseStraightenInkModeLastPos; + private Point _pauseStraightenInkModeStartPos; + private bool _pauseStraightenInkModeTracking; + + private void TryPauseStraightenInkMode() + { + if (!Settings.Canvas.PauseStraightenLine) return; + if (!_pauseStraightenInkModeTracking) return; + if (inkCanvas.EditingMode != InkCanvasEditingMode.Ink) return; + if (drawingShapeMode != 0) return; + + var start = _pauseStraightenInkModeStartPos; + var end = _pauseStraightenInkModeLastPos; + double lineLength = GetDistance(start, end); + if (lineLength < 2) return; + + // Commit current stroke by briefly switching mode + inkCanvas.EditingMode = InkCanvasEditingMode.None; + inkCanvas.EditingMode = InkCanvasEditingMode.Ink; + + // The just-committed stroke should now be last in inkCanvas.Strokes + if (inkCanvas.Strokes.Count == 0) return; + var stroke = inkCanvas.Strokes[inkCanvas.Strokes.Count - 1]; + if (stroke.StylusPoints.Count < 2) return; + + var newPoints = new StylusPointCollection(); + newPoints.Add(new StylusPoint(start.X, start.Y, 0.5f)); + if (lineLength > 100) + { + newPoints.Add(new StylusPoint(start.X + (end.X - start.X) / 3.0, start.Y + (end.Y - start.Y) / 3.0, 0.5f)); + newPoints.Add(new StylusPoint(start.X + (end.X - start.X) * 2.0 / 3.0, start.Y + (end.Y - start.Y) * 2.0 / 3.0, 0.5f)); + } + newPoints.Add(new StylusPoint(end.X, end.Y, 0.5f)); + stroke.StylusPoints = newPoints; + + _pauseStraightenInkModeTracking = false; + } + + private void CancelPauseStraightenTimer(int stylusId) + { + if (_pauseStraightenTimers.TryGetValue(stylusId, out var timer)) + { + timer.Stop(); + _pauseStraightenTimers.Remove(stylusId); + } + } + + private void TryPauseStraighten(int stylusId) + { + if (!Settings.Canvas.PauseStraightenLine) { Debug.WriteLine("PauseStraighten: disabled"); return; } + var strokeVisual = StrokeVisualList.TryGetValue(stylusId, out var sv) ? sv : null; + if (strokeVisual?.Stroke == null) { Debug.WriteLine($"PauseStraighten: no stroke for id={stylusId}"); return; } + var stroke = strokeVisual.Stroke; + Debug.WriteLine($"PauseStraighten: points={stroke.StylusPoints.Count}"); + if (stroke.StylusPoints.Count < 2) return; + + var start = stroke.StylusPoints[0].ToPoint(); + var end = stroke.StylusPoints[stroke.StylusPoints.Count - 1].ToPoint(); + double lineLength = GetDistance(start, end); + Debug.WriteLine($"PauseStraighten: length={lineLength:F1}, STRAIGHTENING!"); + + var newPoints = new StylusPointCollection(); + newPoints.Add(new StylusPoint(start.X, start.Y, 0.5f)); + if (lineLength > 100) + { + newPoints.Add(new StylusPoint(start.X + (end.X - start.X) / 3.0, start.Y + (end.Y - start.Y) / 3.0, 0.5f)); + newPoints.Add(new StylusPoint(start.X + (end.X - start.X) * 2.0 / 3.0, start.Y + (end.Y - start.Y) * 2.0 / 3.0, 0.5f)); + } + newPoints.Add(new StylusPoint(end.X, end.Y, 0.5f)); + stroke.StylusPoints = newPoints; + strokeVisual.ForceRedraw(); + } + /// /// 获取触摸按下点的编辑模式方法 /// @@ -1191,6 +1329,7 @@ namespace Ink_Canvas var touchId = e.TouchDevice.Id; var p = e.GetTouchPoint(inkCanvas).Position; _activeRealtimeTouchStrokeIds.Add(touchId); + CancelPauseStraightenTimer(touchId); InitializeRealtimeBrushTipStateFromPoint(touchId, p); var sv = GetStrokeVisual(touchId); TryAppendRealtimeVelocityBrushTipPoint(sv, touchId, p); @@ -1214,6 +1353,7 @@ namespace Ink_Canvas var touchId = e.TouchDevice.Id; var p = e.GetTouchPoint(inkCanvas).Position; _activeTouchStrokeIds.Add(touchId); + CancelPauseStraightenTimer(touchId); var sv = GetStrokeVisual(touchId); sv.Add(new StylusPoint(p.X, p.Y, 0.5f)); sv.Redraw(); @@ -1348,6 +1488,7 @@ namespace Ink_Canvas var sv = GetStrokeVisual(touchId); if (TryAppendRealtimeVelocityBrushTipPoint(sv, touchId, p)) sv.ForceRedraw(); + ResetPauseStraightenTimer(touchId); } catch (Exception ex) { @@ -1364,6 +1505,7 @@ namespace Ink_Canvas var sv = GetStrokeVisual(touchId); sv.Add(new StylusPoint(p.X, p.Y, 0.5f)); sv.Redraw(); + ResetPauseStraightenTimer(touchId); } catch (Exception ex) { @@ -1424,6 +1566,7 @@ namespace Ink_Canvas VisualCanvasList.Remove(touchId); TouchDownPointsList.Remove(touchId); CleanupRealtimeBrushTipState(touchId); + CancelPauseStraightenTimer(touchId); _activeRealtimeTouchStrokeIds.Remove(touchId); } } @@ -1452,6 +1595,7 @@ namespace Ink_Canvas VisualCanvasList.Remove(touchId); TouchDownPointsList.Remove(touchId); CleanupRealtimeBrushTipState(touchId); + CancelPauseStraightenTimer(touchId); _activeTouchStrokeIds.Remove(touchId); } } diff --git a/Ink Canvas/Properties/Strings.resx b/Ink Canvas/Properties/Strings.resx index 9be513c4..c617ff81 100644 --- a/Ink Canvas/Properties/Strings.resx +++ b/Ink Canvas/Properties/Strings.resx @@ -630,6 +630,12 @@ 开启后,当绘制的直线超过设定长度阈值时,将自动调整为完美直线。灵敏度范围0.05-2.0,越小要求越严格,越弯曲的线条越不容易被拉直;值越大越容易识别为直线。高精度模式下,每隔10像素取一个计数点,获取更准确的平均值用于判断。 + + 停顿拉直 + + + 书写中停顿时,自动将当前笔画拉直为直线(需同时开启直线自动拉直)。 + 直线端点吸附 diff --git a/Ink Canvas/Resources/Settings.cs b/Ink Canvas/Resources/Settings.cs index 2c3ebed1..57c46e52 100644 --- a/Ink Canvas/Resources/Settings.cs +++ b/Ink Canvas/Resources/Settings.cs @@ -108,6 +108,8 @@ namespace Ink_Canvas public int AutoStraightenLineThreshold { get; set; } = 80; // 直线自动拉直的长度阈值(像素) [JsonProperty("highPrecisionLineStraighten")] public bool HighPrecisionLineStraighten { get; set; } = true; // 是否启用高精度直线拉直 + [JsonProperty("pauseStraightenLine")] + public bool PauseStraightenLine { get; set; } = false; // 是否启用停顿拉直(书写中停顿时自动拉直笔画) [JsonProperty("lineEndpointSnapping")] public bool LineEndpointSnapping { get; set; } = true; // 是否启用直线端点吸附 [JsonProperty("lineEndpointSnappingThreshold")] diff --git a/Ink Canvas/Windows/SettingsViews/Pages/InkRecognitionPage.xaml b/Ink Canvas/Windows/SettingsViews/Pages/InkRecognitionPage.xaml index ac652f08..0480b293 100644 --- a/Ink Canvas/Windows/SettingsViews/Pages/InkRecognitionPage.xaml +++ b/Ink Canvas/Windows/SettingsViews/Pages/InkRecognitionPage.xaml @@ -157,6 +157,13 @@ OffContent="{DynamicResource Common_Off}" Toggled="ToggleSwitchHighPrecisionLineStraighten_Toggled" /> + + + diff --git a/Ink Canvas/Windows/SettingsViews/Pages/InkRecognitionPage.xaml.cs b/Ink Canvas/Windows/SettingsViews/Pages/InkRecognitionPage.xaml.cs index 9bfb8df3..40653417 100644 --- a/Ink Canvas/Windows/SettingsViews/Pages/InkRecognitionPage.xaml.cs +++ b/Ink Canvas/Windows/SettingsViews/Pages/InkRecognitionPage.xaml.cs @@ -52,6 +52,7 @@ namespace Ink_Canvas.Windows.SettingsViews.Pages ToggleSwitchAutoStraightenLine.IsOn = settings.Canvas.AutoStraightenLine; AutoStraightenLineThresholdSlider.Value = settings.Canvas.AutoStraightenLineThreshold; ToggleSwitchHighPrecisionLineStraighten.IsOn = settings.Canvas.HighPrecisionLineStraighten; + ToggleSwitchPauseStraightenLine.IsOn = settings.Canvas.PauseStraightenLine; ToggleSwitchLineEndpointSnapping.IsOn = settings.Canvas.LineEndpointSnapping; } } @@ -192,6 +193,13 @@ namespace Ink_Canvas.Windows.SettingsViews.Pages SettingsManager.SaveSettingsToFile(); } + private void ToggleSwitchPauseStraightenLine_Toggled(object sender, RoutedEventArgs e) + { + if (!_isLoaded) return; + SettingsManager.Settings.Canvas.PauseStraightenLine = ToggleSwitchPauseStraightenLine.IsOn; + SettingsManager.SaveSettingsToFile(); + } + private void ToggleSwitchLineEndpointSnapping_Toggled(object sender, RoutedEventArgs e) { if (!_isLoaded) return;