Merge branch 'net6' into net462
This commit is contained in:
@@ -47,6 +47,372 @@ namespace Ink_Canvas
|
||||
private bool isMultiTouchTimerActive;
|
||||
private bool isPalmEraserActive;
|
||||
private bool palmEraserWasEnabledBeforeMultiTouch;
|
||||
private InkCanvasEditingMode palmEraserPreviousEditingMode = InkCanvasEditingMode.Ink;
|
||||
private readonly Dictionary<int, RealtimeBrushTipState> _realtimeBrushTipStates = new Dictionary<int, RealtimeBrushTipState>();
|
||||
private readonly Guid RealtimeVelocityBrushTipAppliedGuid = new Guid("74E57D95-945F-4A8C-B52A-7D3EF2D4FD5B");
|
||||
internal const int MouseRealtimeStrokeId = -100001;
|
||||
private readonly HashSet<int> _activeRealtimeTouchStrokeIds = new HashSet<int>();
|
||||
private readonly HashSet<int> _activeTouchStrokeIds = new HashSet<int>();
|
||||
|
||||
private sealed class OneEuroFilter
|
||||
{
|
||||
private readonly float _minCutoff;
|
||||
private readonly float _beta;
|
||||
private readonly float _dCutoff;
|
||||
private bool _initialized;
|
||||
private float _xPrev;
|
||||
private float _dxPrev;
|
||||
|
||||
public OneEuroFilter(float minCutoff, float beta, float dCutoff)
|
||||
{
|
||||
_minCutoff = minCutoff;
|
||||
_beta = beta;
|
||||
_dCutoff = dCutoff;
|
||||
}
|
||||
|
||||
public float Filter(float value, float dt, float speed)
|
||||
{
|
||||
if (!_initialized)
|
||||
{
|
||||
_initialized = true;
|
||||
_xPrev = value;
|
||||
_dxPrev = 0f;
|
||||
return value;
|
||||
}
|
||||
|
||||
var dx = (value - _xPrev) / Math.Max(1e-6f, dt);
|
||||
var aD = Alpha(_dCutoff, dt);
|
||||
var dxHat = Lerp(_dxPrev, dx, aD);
|
||||
var a = Alpha(_minCutoff + _beta * speed, dt);
|
||||
var xHat = Lerp(_xPrev, value, a);
|
||||
_xPrev = xHat;
|
||||
_dxPrev = dxHat;
|
||||
return xHat;
|
||||
}
|
||||
|
||||
private static float Alpha(float cutoff, float dt)
|
||||
{
|
||||
var tau = 1f / (2f * (float)Math.PI * Math.Max(1e-3f, cutoff));
|
||||
return 1f / (1f + tau / Math.Max(1e-6f, dt));
|
||||
}
|
||||
|
||||
private static float Lerp(float a, float b, float t) => a + (b - a) * t;
|
||||
}
|
||||
|
||||
private sealed class RealtimeBrushTipState
|
||||
{
|
||||
public float LastRawX { get; set; }
|
||||
public float LastRawY { get; set; }
|
||||
public long LastTimestampMs { get; set; }
|
||||
public float SmoothedSampleRateHz { get; set; } = 120f;
|
||||
public bool SawPressureVariation { get; set; }
|
||||
public bool HasSeed { get; set; }
|
||||
public float LastSmoothX { get; set; }
|
||||
public float LastSmoothY { get; set; }
|
||||
public float LastSmoothPressure { get; set; } = 0.5f;
|
||||
public OneEuroFilter FilterX { get; } = new OneEuroFilter(1.2f, 0.015f, 1f);
|
||||
public OneEuroFilter FilterY { get; } = new OneEuroFilter(1.2f, 0.015f, 1f);
|
||||
public OneEuroFilter FilterPressure { get; } = new OneEuroFilter(1f, 0.02f, 1f);
|
||||
}
|
||||
|
||||
private static long RealtimeNowMs() => Environment.TickCount & 0x7FFFFFFF;
|
||||
|
||||
private static float RealtimeClamp(float x, float min, float max)
|
||||
{
|
||||
if (x < min) return min;
|
||||
if (x > max) return max;
|
||||
return x;
|
||||
}
|
||||
|
||||
private static float WidthToPressure(float width, float baseWidth)
|
||||
{
|
||||
if (baseWidth <= 1e-4f) return 0.5f;
|
||||
var scale = width / baseWidth;
|
||||
return RealtimeClamp((scale - 0.42f) / 1.16f, 0.08f, 1f);
|
||||
}
|
||||
|
||||
private bool ShouldUseRealtimeVelocityBrushTip()
|
||||
{
|
||||
return Settings.Canvas.InkStyle == 3
|
||||
&& Settings.Canvas.VelocityBrushTipMix > 0
|
||||
&& !Settings.Canvas.DisablePressure;
|
||||
}
|
||||
|
||||
private bool ShouldUseRealtimeVelocityBrushTipForTouch()
|
||||
{
|
||||
return Settings.Canvas.InkStyle == 3
|
||||
&& Settings.Canvas.VelocityBrushTipMix > 0
|
||||
&& !Settings.Canvas.DisablePressure
|
||||
&& drawingShapeMode == 0
|
||||
&& !isPalmEraserActive;
|
||||
}
|
||||
|
||||
internal bool ShouldUseRealtimeVelocityBrushTipForMouse()
|
||||
{
|
||||
return ShouldUseRealtimeVelocityBrushTip()
|
||||
&& drawingShapeMode == 0
|
||||
&& !isPalmEraserActive;
|
||||
}
|
||||
|
||||
private static bool IsTouchStylusDevice(StylusDevice stylusDevice)
|
||||
{
|
||||
return stylusDevice?.TabletDevice?.Type == TabletDeviceType.Touch;
|
||||
}
|
||||
|
||||
internal void EnsureRealtimeStylusPipelineBinding()
|
||||
{
|
||||
if (inkCanvas == null) return;
|
||||
|
||||
inkCanvas.StylusDown -= MainWindow_StylusDown;
|
||||
inkCanvas.StylusMove -= MainWindow_StylusMove;
|
||||
inkCanvas.StylusUp -= MainWindow_StylusUp;
|
||||
|
||||
inkCanvas.StylusDown += MainWindow_StylusDown;
|
||||
inkCanvas.StylusMove += MainWindow_StylusMove;
|
||||
inkCanvas.StylusUp += MainWindow_StylusUp;
|
||||
|
||||
if (ShouldUseRealtimeVelocityBrushTip()
|
||||
&& inkCanvas.EditingMode != InkCanvasEditingMode.EraseByPoint
|
||||
&& inkCanvas.EditingMode != InkCanvasEditingMode.EraseByStroke
|
||||
&& inkCanvas.EditingMode != InkCanvasEditingMode.Select)
|
||||
{
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
||||
}
|
||||
else if (!ShouldUseRealtimeVelocityBrushTip()
|
||||
&& inkCanvas.EditingMode == InkCanvasEditingMode.None)
|
||||
{
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeRealtimeBrushTipState(int stylusId, StylusDownEventArgs e)
|
||||
{
|
||||
if (!ShouldUseRealtimeVelocityBrushTip())
|
||||
{
|
||||
_realtimeBrushTipStates.Remove(stylusId);
|
||||
return;
|
||||
}
|
||||
|
||||
var startPoint = e.GetPosition(this);
|
||||
_realtimeBrushTipStates[stylusId] = new RealtimeBrushTipState
|
||||
{
|
||||
LastRawX = (float)startPoint.X,
|
||||
LastRawY = (float)startPoint.Y,
|
||||
LastTimestampMs = RealtimeNowMs()
|
||||
};
|
||||
}
|
||||
|
||||
private void InitializeRealtimeBrushTipStateFromPoint(int strokeId, Point startPoint)
|
||||
{
|
||||
if (!ShouldUseRealtimeVelocityBrushTipForTouch() && strokeId != MouseRealtimeStrokeId)
|
||||
{
|
||||
_realtimeBrushTipStates.Remove(strokeId);
|
||||
return;
|
||||
}
|
||||
if (!ShouldUseRealtimeVelocityBrushTipForMouse() && strokeId == MouseRealtimeStrokeId)
|
||||
{
|
||||
_realtimeBrushTipStates.Remove(strokeId);
|
||||
return;
|
||||
}
|
||||
|
||||
_realtimeBrushTipStates[strokeId] = new RealtimeBrushTipState
|
||||
{
|
||||
LastRawX = (float)startPoint.X,
|
||||
LastRawY = (float)startPoint.Y,
|
||||
LastTimestampMs = RealtimeNowMs()
|
||||
};
|
||||
}
|
||||
|
||||
private void CleanupRealtimeBrushTipState(int stylusId)
|
||||
{
|
||||
_realtimeBrushTipStates.Remove(stylusId);
|
||||
}
|
||||
|
||||
private bool TryAppendRealtimeVelocityBrushTipPoints(StrokeVisual strokeVisual, StylusEventArgs e)
|
||||
{
|
||||
if (!ShouldUseRealtimeVelocityBrushTip() || strokeVisual == null || e?.StylusDevice == null)
|
||||
return false;
|
||||
|
||||
if (!_realtimeBrushTipStates.TryGetValue(e.StylusDevice.Id, out var state))
|
||||
return false;
|
||||
|
||||
var stylusPointCollection = e.GetStylusPoints(this);
|
||||
if (stylusPointCollection == null || stylusPointCollection.Count == 0)
|
||||
return true;
|
||||
|
||||
var mix = RealtimeClamp((float)Settings.Canvas.VelocityBrushTipMix, 0f, 1f);
|
||||
var appended = false;
|
||||
var baseWidth = (float)Math.Max(0.35,
|
||||
strokeVisual.Stroke?.DrawingAttributes?.Width ?? inkCanvas.DefaultDrawingAttributes.Width);
|
||||
|
||||
foreach (StylusPoint rawPoint in stylusPointCollection)
|
||||
{
|
||||
var nowMs = RealtimeNowMs();
|
||||
var dtMs = Math.Max(1L, nowMs - state.LastTimestampMs);
|
||||
var dt = dtMs / 1000f;
|
||||
var sampleRate = 1f / Math.Max(1e-4f, dt);
|
||||
state.SmoothedSampleRateHz = state.SmoothedSampleRateHz * 0.85f + sampleRate * 0.15f;
|
||||
|
||||
var rawX = (float)rawPoint.X;
|
||||
var rawY = (float)rawPoint.Y;
|
||||
var dx = rawX - state.LastRawX;
|
||||
var dy = rawY - state.LastRawY;
|
||||
var dist = (float)Math.Sqrt(dx * dx + dy * dy);
|
||||
var speed = dist / dt;
|
||||
|
||||
var filteredX = state.FilterX.Filter(rawX, dt, speed);
|
||||
var filteredY = state.FilterY.Filter(rawY, dt, speed);
|
||||
|
||||
var hwPressure = RealtimeClamp((float)rawPoint.PressureFactor, 0f, 1f);
|
||||
if (Math.Abs(hwPressure - 0.5f) > 0.02f)
|
||||
state.SawPressureVariation = true;
|
||||
var usePressure = state.SawPressureVariation && hwPressure > 0f;
|
||||
|
||||
var width = baseWidth;
|
||||
if (usePressure)
|
||||
width *= 0.25f + 0.75f * hwPressure;
|
||||
var speedNormalization = 1800f + state.SmoothedSampleRateHz * 3.5f;
|
||||
width *= RealtimeClamp(1.15f - (speed / speedNormalization), 0.45f, 1.25f);
|
||||
var speedPressure = WidthToPressure(width, baseWidth);
|
||||
|
||||
var pressure = usePressure
|
||||
? ((1f - mix) * hwPressure + mix * speedPressure)
|
||||
: speedPressure;
|
||||
pressure = RealtimeClamp(pressure, 0.08f, 1f);
|
||||
pressure = state.FilterPressure.Filter(pressure, dt, speed);
|
||||
|
||||
// 高频采样时做最小距离门限,避免点爆炸导致实时重绘卡顿
|
||||
var minDist = state.SmoothedSampleRateHz > 160f ? 0.55f
|
||||
: state.SmoothedSampleRateHz > 90f ? 0.4f
|
||||
: 0.25f;
|
||||
if (dist < minDist && state.HasSeed)
|
||||
{
|
||||
state.LastRawX = rawX;
|
||||
state.LastRawY = rawY;
|
||||
state.LastTimestampMs = nowMs;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!state.HasSeed)
|
||||
{
|
||||
state.HasSeed = true;
|
||||
state.LastSmoothX = filteredX;
|
||||
state.LastSmoothY = filteredY;
|
||||
state.LastSmoothPressure = pressure;
|
||||
strokeVisual.Add(new StylusPoint(filteredX, filteredY, pressure));
|
||||
}
|
||||
else
|
||||
{
|
||||
// 采用中点链减抖:保持实时笔锋同时降低折线锯齿
|
||||
var midX = (state.LastSmoothX + filteredX) * 0.5f;
|
||||
var midY = (state.LastSmoothY + filteredY) * 0.5f;
|
||||
var midPressure = (state.LastSmoothPressure + pressure) * 0.5f;
|
||||
strokeVisual.Add(new StylusPoint(midX, midY, midPressure));
|
||||
state.LastSmoothX = filteredX;
|
||||
state.LastSmoothY = filteredY;
|
||||
state.LastSmoothPressure = pressure;
|
||||
}
|
||||
|
||||
state.LastRawX = rawX;
|
||||
state.LastRawY = rawY;
|
||||
state.LastTimestampMs = nowMs;
|
||||
appended = true;
|
||||
}
|
||||
|
||||
var committedStroke = strokeVisual.Stroke;
|
||||
if (appended && committedStroke != null)
|
||||
{
|
||||
if (committedStroke.DrawingAttributes != null)
|
||||
committedStroke.DrawingAttributes.IgnorePressure = false;
|
||||
if (!committedStroke.ContainsPropertyData(RealtimeVelocityBrushTipAppliedGuid))
|
||||
committedStroke.AddPropertyData(RealtimeVelocityBrushTipAppliedGuid, true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryAppendRealtimeVelocityBrushTipPoint(StrokeVisual strokeVisual, int strokeId, Point point, float rawPressure = 0.5f)
|
||||
{
|
||||
var allow = strokeId == MouseRealtimeStrokeId
|
||||
? ShouldUseRealtimeVelocityBrushTipForMouse()
|
||||
: ShouldUseRealtimeVelocityBrushTipForTouch();
|
||||
if (!allow || strokeVisual == null)
|
||||
return false;
|
||||
if (!_realtimeBrushTipStates.TryGetValue(strokeId, out var state))
|
||||
return false;
|
||||
|
||||
var mix = RealtimeClamp((float)Settings.Canvas.VelocityBrushTipMix, 0f, 1f);
|
||||
var nowMs = RealtimeNowMs();
|
||||
var dtMs = Math.Max(1L, nowMs - state.LastTimestampMs);
|
||||
var dt = dtMs / 1000f;
|
||||
var sampleRate = 1f / Math.Max(1e-4f, dt);
|
||||
state.SmoothedSampleRateHz = state.SmoothedSampleRateHz * 0.85f + sampleRate * 0.15f;
|
||||
var baseWidth = (float)Math.Max(0.35,
|
||||
strokeVisual.Stroke?.DrawingAttributes?.Width ?? inkCanvas.DefaultDrawingAttributes.Width);
|
||||
|
||||
var rawX = (float)point.X;
|
||||
var rawY = (float)point.Y;
|
||||
var dx = rawX - state.LastRawX;
|
||||
var dy = rawY - state.LastRawY;
|
||||
var dist = (float)Math.Sqrt(dx * dx + dy * dy);
|
||||
var speed = dist / dt;
|
||||
|
||||
var filteredX = state.FilterX.Filter(rawX, dt, speed);
|
||||
var filteredY = state.FilterY.Filter(rawY, dt, speed);
|
||||
|
||||
rawPressure = RealtimeClamp(rawPressure, 0f, 1f);
|
||||
if (Math.Abs(rawPressure - 0.5f) > 0.02f)
|
||||
state.SawPressureVariation = true;
|
||||
var usePressure = state.SawPressureVariation && rawPressure > 0f;
|
||||
|
||||
var width = baseWidth;
|
||||
if (usePressure)
|
||||
width *= 0.25f + 0.75f * rawPressure;
|
||||
var speedNormalization = 1800f + state.SmoothedSampleRateHz * 3.5f;
|
||||
width *= RealtimeClamp(1.15f - (speed / speedNormalization), 0.45f, 1.25f);
|
||||
var speedPressure = WidthToPressure(width, baseWidth);
|
||||
|
||||
var pressure = usePressure
|
||||
? ((1f - mix) * rawPressure + mix * speedPressure)
|
||||
: speedPressure;
|
||||
pressure = RealtimeClamp(pressure, 0.08f, 1f);
|
||||
pressure = state.FilterPressure.Filter(pressure, dt, speed);
|
||||
|
||||
var minDist = state.SmoothedSampleRateHz > 160f ? 0.55f
|
||||
: state.SmoothedSampleRateHz > 90f ? 0.4f
|
||||
: 0.25f;
|
||||
if (dist < minDist && state.HasSeed)
|
||||
{
|
||||
state.LastRawX = rawX;
|
||||
state.LastRawY = rawY;
|
||||
state.LastTimestampMs = nowMs;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!state.HasSeed)
|
||||
{
|
||||
state.HasSeed = true;
|
||||
state.LastSmoothX = filteredX;
|
||||
state.LastSmoothY = filteredY;
|
||||
state.LastSmoothPressure = pressure;
|
||||
strokeVisual.Add(new StylusPoint(filteredX, filteredY, pressure));
|
||||
}
|
||||
else
|
||||
{
|
||||
var midX = (state.LastSmoothX + filteredX) * 0.5f;
|
||||
var midY = (state.LastSmoothY + filteredY) * 0.5f;
|
||||
var midPressure = (state.LastSmoothPressure + pressure) * 0.5f;
|
||||
strokeVisual.Add(new StylusPoint(midX, midY, midPressure));
|
||||
state.LastSmoothX = filteredX;
|
||||
state.LastSmoothY = filteredY;
|
||||
state.LastSmoothPressure = pressure;
|
||||
}
|
||||
|
||||
state.LastRawX = rawX;
|
||||
state.LastRawY = rawY;
|
||||
state.LastTimestampMs = nowMs;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存画布上的非笔画元素(如图片、媒体元素等)
|
||||
@@ -233,8 +599,7 @@ namespace Ink_Canvas
|
||||
if (palmEraserWasEnabledBeforeMultiTouch)
|
||||
{
|
||||
Settings.Canvas.EnablePalmEraser = true;
|
||||
if (ToggleSwitchEnablePalmEraser != null)
|
||||
ToggleSwitchEnablePalmEraser.IsOn = true;
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -259,8 +624,7 @@ namespace Ink_Canvas
|
||||
|
||||
palmEraserWasEnabledBeforeMultiTouch = Settings.Canvas.EnablePalmEraser;
|
||||
Settings.Canvas.EnablePalmEraser = false;
|
||||
if (ToggleSwitchEnablePalmEraser != null)
|
||||
ToggleSwitchEnablePalmEraser.IsOn = false;
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -340,6 +704,9 @@ namespace Ink_Canvas
|
||||
/// </remarks>
|
||||
private void MainWindow_StylusDown(object sender, StylusDownEventArgs e)
|
||||
{
|
||||
if (IsTouchStylusDevice(e.StylusDevice))
|
||||
return;
|
||||
|
||||
// 检查手写笔点击是否发生在浮动栏区域,如果是则允许事件传播到浮动栏按钮
|
||||
var stylusPoint = e.GetPosition(this);
|
||||
var floatingBarBounds = ViewboxFloatingBar.TransformToAncestor(this).TransformBounds(
|
||||
@@ -375,7 +742,9 @@ namespace Ink_Canvas
|
||||
}
|
||||
if (inkCanvas.EditingMode != InkCanvasEditingMode.EraseByStroke)
|
||||
{
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
|
||||
inkCanvas.EditingMode = ShouldUseRealtimeVelocityBrushTip()
|
||||
? InkCanvasEditingMode.None
|
||||
: InkCanvasEditingMode.Ink;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -392,6 +761,7 @@ namespace Ink_Canvas
|
||||
|| inkCanvas.EditingMode == InkCanvasEditingMode.EraseByStroke
|
||||
|| inkCanvas.EditingMode == InkCanvasEditingMode.Select) return;
|
||||
|
||||
InitializeRealtimeBrushTipState(e.StylusDevice.Id, e);
|
||||
TouchDownPointsList[e.StylusDevice.Id] = InkCanvasEditingMode.None;
|
||||
}
|
||||
|
||||
@@ -421,6 +791,9 @@ namespace Ink_Canvas
|
||||
/// </remarks>
|
||||
private async void MainWindow_StylusUp(object sender, StylusEventArgs e)
|
||||
{
|
||||
if (IsTouchStylusDevice(e.StylusDevice))
|
||||
return;
|
||||
|
||||
if (drawingShapeMode != 0)
|
||||
{
|
||||
// 重置触摸状态
|
||||
@@ -491,6 +864,7 @@ namespace Ink_Canvas
|
||||
StrokeVisualList.Remove(e.StylusDevice.Id);
|
||||
VisualCanvasList.Remove(e.StylusDevice.Id);
|
||||
TouchDownPointsList.Remove(e.StylusDevice.Id);
|
||||
CleanupRealtimeBrushTipState(e.StylusDevice.Id);
|
||||
if (StrokeVisualList.Count == 0 || VisualCanvasList.Count == 0 || TouchDownPointsList.Count == 0)
|
||||
{
|
||||
// 只清除手写笔预览相关的Canvas,不清除所有子元素
|
||||
@@ -534,6 +908,9 @@ namespace Ink_Canvas
|
||||
{
|
||||
try
|
||||
{
|
||||
if (IsTouchStylusDevice(e.StylusDevice))
|
||||
return;
|
||||
|
||||
if (drawingShapeMode != 0)
|
||||
{
|
||||
if (isTouchDown)
|
||||
@@ -553,28 +930,18 @@ namespace Ink_Canvas
|
||||
|
||||
|
||||
var strokeVisual = GetStrokeVisual(e.StylusDevice.Id);
|
||||
var stylusPointCollection = e.GetStylusPoints(this);
|
||||
foreach (var stylusPoint in stylusPointCollection)
|
||||
strokeVisual.Add(new StylusPoint(stylusPoint.X, stylusPoint.Y, stylusPoint.PressureFactor));
|
||||
var isHandledByRealtime = TryAppendRealtimeVelocityBrushTipPoints(strokeVisual, e);
|
||||
if (!isHandledByRealtime)
|
||||
{
|
||||
var stylusPointCollection = e.GetStylusPoints(this);
|
||||
foreach (var stylusPoint in stylusPointCollection)
|
||||
strokeVisual.Add(new StylusPoint(stylusPoint.X, stylusPoint.Y, stylusPoint.PressureFactor));
|
||||
}
|
||||
|
||||
// 实时笔锋:混合度 > 0 时在绘制过程中更新压感并整笔重绘预览;混合为 0 时与普通过程一致用增量 Redraw,避免每点 ForceRedraw 整笔清空(长笔画卡顿)。
|
||||
var committedStroke = strokeVisual.Stroke;
|
||||
if (committedStroke != null
|
||||
&& Settings.Canvas.InkStyle == 3
|
||||
&& Settings.Canvas.VelocityBrushTipMix > 0
|
||||
&& !Settings.Canvas.DisablePressure
|
||||
&& penType == 0
|
||||
&& committedStroke.DrawingAttributes != null
|
||||
&& !committedStroke.DrawingAttributes.IsHighlighter
|
||||
&& committedStroke.StylusPoints.Count >= 3)
|
||||
{
|
||||
ApplyVelocityBrushTipFromSpeed(committedStroke);
|
||||
if (isHandledByRealtime)
|
||||
strokeVisual.ForceRedraw();
|
||||
}
|
||||
else
|
||||
{
|
||||
strokeVisual.Redraw();
|
||||
}
|
||||
}
|
||||
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
||||
}
|
||||
@@ -813,6 +1180,51 @@ namespace Ink_Canvas
|
||||
lastTouchDownTime = DateTime.Now;
|
||||
dec.Add(e.TouchDevice.Id);
|
||||
|
||||
if (ShouldUseRealtimeVelocityBrushTipForTouch()
|
||||
&& inkCanvas.EditingMode != InkCanvasEditingMode.EraseByPoint
|
||||
&& inkCanvas.EditingMode != InkCanvasEditingMode.EraseByStroke
|
||||
&& inkCanvas.EditingMode != InkCanvasEditingMode.Select)
|
||||
{
|
||||
try
|
||||
{
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
||||
var touchId = e.TouchDevice.Id;
|
||||
var p = e.GetTouchPoint(inkCanvas).Position;
|
||||
_activeRealtimeTouchStrokeIds.Add(touchId);
|
||||
InitializeRealtimeBrushTipStateFromPoint(touchId, p);
|
||||
var sv = GetStrokeVisual(touchId);
|
||||
TryAppendRealtimeVelocityBrushTipPoint(sv, touchId, p);
|
||||
sv.ForceRedraw();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(ex);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ((isInMultiTouchMode || Settings.Gesture.IsEnableMultiTouchMode)
|
||||
&& inkCanvas.EditingMode != InkCanvasEditingMode.EraseByPoint
|
||||
&& inkCanvas.EditingMode != InkCanvasEditingMode.EraseByStroke
|
||||
&& inkCanvas.EditingMode != InkCanvasEditingMode.Select)
|
||||
{
|
||||
try
|
||||
{
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
||||
var touchId = e.TouchDevice.Id;
|
||||
var p = e.GetTouchPoint(inkCanvas).Position;
|
||||
_activeTouchStrokeIds.Add(touchId);
|
||||
var sv = GetStrokeVisual(touchId);
|
||||
sv.Add(new StylusPoint(p.X, p.Y, 0.5f));
|
||||
sv.Redraw();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(ex);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (Settings.Canvas.EnablePalmEraser && !isPalmEraserActive && drawingShapeMode == 0)
|
||||
{
|
||||
var touchPoint = e.GetTouchPoint(inkCanvas);
|
||||
@@ -848,6 +1260,7 @@ namespace Ink_Canvas
|
||||
|
||||
if (Settings.Advanced.IsSpecialScreen)
|
||||
boundWidth *= Settings.Advanced.TouchMultiplier;
|
||||
palmEraserPreviousEditingMode = inkCanvas.EditingMode;
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.EraseByPoint;
|
||||
isPalmEraserActive = true;
|
||||
|
||||
@@ -923,6 +1336,40 @@ namespace Ink_Canvas
|
||||
var touchPoint = e.GetTouchPoint(inkCanvas);
|
||||
EraserOverlay_PointerMove(sender, touchPoint.Position);
|
||||
}
|
||||
|
||||
var touchId = e.TouchDevice.Id;
|
||||
if (ShouldUseRealtimeVelocityBrushTipForTouch())
|
||||
{
|
||||
if (!_activeRealtimeTouchStrokeIds.Contains(touchId))
|
||||
return;
|
||||
try
|
||||
{
|
||||
var p = e.GetTouchPoint(inkCanvas).Position;
|
||||
var sv = GetStrokeVisual(touchId);
|
||||
if (TryAppendRealtimeVelocityBrushTipPoint(sv, touchId, p))
|
||||
sv.ForceRedraw();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(ex);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (_activeTouchStrokeIds.Contains(touchId))
|
||||
{
|
||||
try
|
||||
{
|
||||
var p = e.GetTouchPoint(inkCanvas).Position;
|
||||
var sv = GetStrokeVisual(touchId);
|
||||
sv.Add(new StylusPoint(p.X, p.Y, 0.5f));
|
||||
sv.Redraw();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -949,6 +1396,66 @@ namespace Ink_Canvas
|
||||
/// </remarks>
|
||||
private void InkCanvas_PreviewTouchUp(object sender, TouchEventArgs e)
|
||||
{
|
||||
var touchId = e.TouchDevice.Id;
|
||||
if (_activeRealtimeTouchStrokeIds.Contains(touchId))
|
||||
{
|
||||
try
|
||||
{
|
||||
var sv = GetStrokeVisual(touchId);
|
||||
sv?.ForceRedraw();
|
||||
var stroke = sv?.Stroke;
|
||||
if (stroke != null)
|
||||
{
|
||||
if (!stroke.ContainsPropertyData(RealtimeVelocityBrushTipAppliedGuid))
|
||||
stroke.AddPropertyData(RealtimeVelocityBrushTipAppliedGuid, true);
|
||||
inkCanvas.Strokes.Add(stroke);
|
||||
inkCanvas_StrokeCollected(inkCanvas, new InkCanvasStrokeCollectedEventArgs(stroke));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (VisualCanvasList.TryGetValue(touchId, out var visualCanvas) && inkCanvas.Children.Contains(visualCanvas))
|
||||
inkCanvas.Children.Remove(visualCanvas);
|
||||
StrokeVisualList.Remove(touchId);
|
||||
VisualCanvasList.Remove(touchId);
|
||||
TouchDownPointsList.Remove(touchId);
|
||||
CleanupRealtimeBrushTipState(touchId);
|
||||
_activeRealtimeTouchStrokeIds.Remove(touchId);
|
||||
}
|
||||
}
|
||||
else if (_activeTouchStrokeIds.Contains(touchId))
|
||||
{
|
||||
try
|
||||
{
|
||||
var sv = GetStrokeVisual(touchId);
|
||||
sv?.Redraw();
|
||||
var stroke = sv?.Stroke;
|
||||
if (stroke != null)
|
||||
{
|
||||
inkCanvas.Strokes.Add(stroke);
|
||||
inkCanvas_StrokeCollected(inkCanvas, new InkCanvasStrokeCollectedEventArgs(stroke));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (VisualCanvasList.TryGetValue(touchId, out var visualCanvas) && inkCanvas.Children.Contains(visualCanvas))
|
||||
inkCanvas.Children.Remove(visualCanvas);
|
||||
StrokeVisualList.Remove(touchId);
|
||||
VisualCanvasList.Remove(touchId);
|
||||
TouchDownPointsList.Remove(touchId);
|
||||
CleanupRealtimeBrushTipState(touchId);
|
||||
_activeTouchStrokeIds.Remove(touchId);
|
||||
}
|
||||
}
|
||||
|
||||
if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint && !isPalmEraserActive)
|
||||
{
|
||||
return;
|
||||
@@ -1021,6 +1528,11 @@ namespace Ink_Canvas
|
||||
{
|
||||
isPalmEraserActive = false;
|
||||
DisableEraserOverlay();
|
||||
if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint)
|
||||
{
|
||||
inkCanvas.EditingMode = palmEraserPreviousEditingMode;
|
||||
SetCursorBasedOnEditingMode(inkCanvas);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user