7 changed files with 473 additions and 396 deletions
@ -0,0 +1,221 @@ |
|||
#nullable enable |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Diagnostics; |
|||
using System.Threading; |
|||
|
|||
using Avalonia; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Input; |
|||
using Avalonia.Media; |
|||
using Avalonia.Threading; |
|||
|
|||
namespace ControlCatalog.Pages; |
|||
|
|||
public class PointerCanvas : Control |
|||
{ |
|||
private readonly Stopwatch _stopwatch = Stopwatch.StartNew(); |
|||
private int _events; |
|||
private IDisposable? _statusUpdated; |
|||
private Dictionary<int, PointerPoints> _pointers = new(); |
|||
private PointerPointProperties? _lastProperties; |
|||
class PointerPoints |
|||
{ |
|||
struct CanvasPoint |
|||
{ |
|||
public IBrush Brush; |
|||
public Point Point; |
|||
public double Radius; |
|||
public double? Pressure; |
|||
} |
|||
|
|||
readonly CanvasPoint[] _points = new CanvasPoint[1000]; |
|||
int _index; |
|||
|
|||
public void Render(DrawingContext context, bool drawPoints) |
|||
{ |
|||
CanvasPoint? prev = null; |
|||
for (var c = 0; c < _points.Length; c++) |
|||
{ |
|||
var i = (c + _index) % _points.Length; |
|||
var pt = _points[i]; |
|||
var pressure = (pt.Pressure ?? prev?.Pressure ?? 0.5); |
|||
var thickness = pressure * 10; |
|||
var radius = pressure * pt.Radius; |
|||
|
|||
if (drawPoints) |
|||
{ |
|||
if (pt.Brush != null) |
|||
{ |
|||
context.DrawEllipse(pt.Brush, null, pt.Point, radius, radius); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
if (prev.HasValue && prev.Value.Brush != null && pt.Brush != null |
|||
&& prev.Value.Pressure != null && pt.Pressure != null) |
|||
{ |
|||
var linePen = new Pen(Brushes.Black, thickness, null, PenLineCap.Round, PenLineJoin.Round); |
|||
context.DrawLine(linePen, prev.Value.Point, pt.Point); |
|||
} |
|||
} |
|||
prev = pt; |
|||
} |
|||
|
|||
} |
|||
|
|||
void AddPoint(Point pt, IBrush brush, double radius, float? pressure = null) |
|||
{ |
|||
_points[_index] = new CanvasPoint { Point = pt, Brush = brush, Radius = radius, Pressure = pressure }; |
|||
_index = (_index + 1) % _points.Length; |
|||
} |
|||
|
|||
public void HandleEvent(PointerEventArgs e, Visual v) |
|||
{ |
|||
e.Handled = true; |
|||
var currentPoint = e.GetCurrentPoint(v); |
|||
if (e.RoutedEvent == PointerPressedEvent) |
|||
AddPoint(currentPoint.Position, Brushes.Green, 10); |
|||
else if (e.RoutedEvent == PointerReleasedEvent) |
|||
AddPoint(currentPoint.Position, Brushes.Red, 10); |
|||
else |
|||
{ |
|||
var pts = e.GetIntermediatePoints(v); |
|||
for (var c = 0; c < pts.Count; c++) |
|||
{ |
|||
var pt = pts[c]; |
|||
AddPoint(pt.Position, c == pts.Count - 1 ? Brushes.Blue : Brushes.Black, |
|||
c == pts.Count - 1 ? 5 : 2, pt.Properties.Pressure); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
private int _threadSleep; |
|||
public static DirectProperty<PointerCanvas, int> ThreadSleepProperty = |
|||
AvaloniaProperty.RegisterDirect<PointerCanvas, int>(nameof(ThreadSleep), c => c.ThreadSleep, (c, v) => c.ThreadSleep = v); |
|||
|
|||
public int ThreadSleep |
|||
{ |
|||
get => _threadSleep; |
|||
set => SetAndRaise(ThreadSleepProperty, ref _threadSleep, value); |
|||
} |
|||
|
|||
private bool _drawOnlyPoints; |
|||
public static DirectProperty<PointerCanvas, bool> DrawOnlyPointsProperty = |
|||
AvaloniaProperty.RegisterDirect<PointerCanvas, bool>(nameof(DrawOnlyPoints), c => c.DrawOnlyPoints, (c, v) => c.DrawOnlyPoints = v); |
|||
|
|||
public bool DrawOnlyPoints |
|||
{ |
|||
get => _drawOnlyPoints; |
|||
set => SetAndRaise(DrawOnlyPointsProperty, ref _drawOnlyPoints, value); |
|||
} |
|||
|
|||
private string? _status; |
|||
public static DirectProperty<PointerCanvas, string?> StatusProperty = |
|||
AvaloniaProperty.RegisterDirect<PointerCanvas, string?>(nameof(DrawOnlyPoints), c => c.Status, (c, v) => c.Status = v, |
|||
defaultBindingMode: Avalonia.Data.BindingMode.TwoWay); |
|||
|
|||
public string? Status |
|||
{ |
|||
get => _status; |
|||
set => SetAndRaise(StatusProperty, ref _status, value); |
|||
} |
|||
|
|||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) |
|||
{ |
|||
base.OnAttachedToVisualTree(e); |
|||
|
|||
_statusUpdated = DispatcherTimer.Run(() => |
|||
{ |
|||
if (_stopwatch.Elapsed.TotalSeconds > 1) |
|||
{ |
|||
Status = $@"Events per second: {(_events / _stopwatch.Elapsed.TotalSeconds)}
|
|||
PointerUpdateKind: {_lastProperties?.PointerUpdateKind} |
|||
IsLeftButtonPressed: {_lastProperties?.IsLeftButtonPressed} |
|||
IsRightButtonPressed: {_lastProperties?.IsRightButtonPressed} |
|||
IsMiddleButtonPressed: {_lastProperties?.IsMiddleButtonPressed} |
|||
IsXButton1Pressed: {_lastProperties?.IsXButton1Pressed} |
|||
IsXButton2Pressed: {_lastProperties?.IsXButton2Pressed} |
|||
IsBarrelButtonPressed: {_lastProperties?.IsBarrelButtonPressed} |
|||
IsEraser: {_lastProperties?.IsEraser} |
|||
IsInverted: {_lastProperties?.IsInverted} |
|||
Pressure: {_lastProperties?.Pressure} |
|||
XTilt: {_lastProperties?.XTilt} |
|||
YTilt: {_lastProperties?.YTilt} |
|||
Twist: {_lastProperties?.Twist}";
|
|||
_stopwatch.Restart(); |
|||
_events = 0; |
|||
} |
|||
|
|||
return true; |
|||
}, TimeSpan.FromMilliseconds(10)); |
|||
} |
|||
|
|||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) |
|||
{ |
|||
base.OnDetachedFromVisualTree(e); |
|||
|
|||
_statusUpdated?.Dispose(); |
|||
} |
|||
|
|||
void HandleEvent(PointerEventArgs e) |
|||
{ |
|||
_events++; |
|||
if (_threadSleep != 0) |
|||
{ |
|||
Thread.Sleep(_threadSleep); |
|||
} |
|||
InvalidateVisual(); |
|||
|
|||
if (e.RoutedEvent == PointerReleasedEvent && e.Pointer.Type == PointerType.Touch) |
|||
{ |
|||
_pointers.Remove(e.Pointer.Id); |
|||
return; |
|||
} |
|||
|
|||
var lastPointer = e.GetCurrentPoint(this); |
|||
_lastProperties = lastPointer.Properties; |
|||
|
|||
if (e.Pointer.Type != PointerType.Pen |
|||
|| lastPointer.Properties.Pressure > 0) |
|||
{ |
|||
if (!_pointers.TryGetValue(e.Pointer.Id, out var pt)) |
|||
_pointers[e.Pointer.Id] = pt = new PointerPoints(); |
|||
pt.HandleEvent(e, this); |
|||
} |
|||
} |
|||
|
|||
public override void Render(DrawingContext context) |
|||
{ |
|||
context.FillRectangle(Brushes.White, Bounds); |
|||
foreach (var pt in _pointers.Values) |
|||
pt.Render(context, _drawOnlyPoints); |
|||
base.Render(context); |
|||
} |
|||
|
|||
protected override void OnPointerPressed(PointerPressedEventArgs e) |
|||
{ |
|||
if (e.ClickCount == 2) |
|||
{ |
|||
_pointers.Clear(); |
|||
InvalidateVisual(); |
|||
return; |
|||
} |
|||
|
|||
HandleEvent(e); |
|||
base.OnPointerPressed(e); |
|||
} |
|||
|
|||
protected override void OnPointerMoved(PointerEventArgs e) |
|||
{ |
|||
HandleEvent(e); |
|||
base.OnPointerMoved(e); |
|||
} |
|||
|
|||
protected override void OnPointerReleased(PointerReleasedEventArgs e) |
|||
{ |
|||
HandleEvent(e); |
|||
base.OnPointerReleased(e); |
|||
} |
|||
} |
|||
@ -0,0 +1,109 @@ |
|||
#nullable enable |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Reactive.Linq; |
|||
|
|||
using Avalonia; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Input; |
|||
using Avalonia.Media; |
|||
using Avalonia.Media.Immutable; |
|||
|
|||
namespace ControlCatalog.Pages; |
|||
|
|||
public class PointerContactsTab : Control |
|||
{ |
|||
class PointerInfo |
|||
{ |
|||
public Point Point { get; set; } |
|||
public Color Color { get; set; } |
|||
} |
|||
|
|||
private static Color[] AllColors = new[] |
|||
{ |
|||
Colors.Aqua, |
|||
Colors.Beige, |
|||
Colors.Chartreuse, |
|||
Colors.Coral, |
|||
Colors.Fuchsia, |
|||
Colors.Crimson, |
|||
Colors.Lavender, |
|||
Colors.Orange, |
|||
Colors.Orchid, |
|||
Colors.ForestGreen, |
|||
Colors.SteelBlue, |
|||
Colors.PapayaWhip, |
|||
Colors.PaleVioletRed, |
|||
Colors.Goldenrod, |
|||
Colors.Maroon, |
|||
Colors.Moccasin, |
|||
Colors.Navy, |
|||
Colors.Wheat, |
|||
Colors.Violet, |
|||
Colors.Sienna, |
|||
Colors.Indigo, |
|||
Colors.Honeydew |
|||
}; |
|||
|
|||
private Dictionary<IPointer, PointerInfo> _pointers = new Dictionary<IPointer, PointerInfo>(); |
|||
|
|||
public PointerContactsTab() |
|||
{ |
|||
ClipToBounds = true; |
|||
} |
|||
|
|||
void UpdatePointer(PointerEventArgs e) |
|||
{ |
|||
if (!_pointers.TryGetValue(e.Pointer, out var info)) |
|||
{ |
|||
if (e.RoutedEvent == PointerMovedEvent) |
|||
return; |
|||
var colors = AllColors.Except(_pointers.Values.Select(c => c.Color)).ToArray(); |
|||
var color = colors[new Random().Next(0, colors.Length - 1)]; |
|||
_pointers[e.Pointer] = info = new PointerInfo { Color = color }; |
|||
} |
|||
|
|||
info.Point = e.GetPosition(this); |
|||
InvalidateVisual(); |
|||
} |
|||
|
|||
protected override void OnPointerPressed(PointerPressedEventArgs e) |
|||
{ |
|||
UpdatePointer(e); |
|||
e.Pointer.Capture(this); |
|||
e.Handled = true; |
|||
base.OnPointerPressed(e); |
|||
} |
|||
|
|||
protected override void OnPointerMoved(PointerEventArgs e) |
|||
{ |
|||
UpdatePointer(e); |
|||
e.Handled = true; |
|||
base.OnPointerMoved(e); |
|||
} |
|||
|
|||
protected override void OnPointerReleased(PointerReleasedEventArgs e) |
|||
{ |
|||
_pointers.Remove(e.Pointer); |
|||
e.Handled = true; |
|||
InvalidateVisual(); |
|||
} |
|||
|
|||
protected override void OnPointerCaptureLost(PointerCaptureLostEventArgs e) |
|||
{ |
|||
_pointers.Remove(e.Pointer); |
|||
InvalidateVisual(); |
|||
} |
|||
|
|||
public override void Render(DrawingContext context) |
|||
{ |
|||
context.FillRectangle(Brushes.Transparent, new Rect(default, Bounds.Size)); |
|||
foreach (var pt in _pointers.Values) |
|||
{ |
|||
var brush = new ImmutableSolidColorBrush(pt.Color); |
|||
|
|||
context.DrawEllipse(brush, null, pt.Point, 75, 75); |
|||
} |
|||
} |
|||
} |
|||
@ -1,394 +0,0 @@ |
|||
#nullable enable |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Diagnostics; |
|||
using System.Linq; |
|||
using System.Reactive.Linq; |
|||
using System.Runtime.InteropServices; |
|||
using System.Threading; |
|||
using Avalonia; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Controls.Documents; |
|||
using Avalonia.Input; |
|||
using Avalonia.Layout; |
|||
using Avalonia.Media; |
|||
using Avalonia.Media.Immutable; |
|||
using Avalonia.Threading; |
|||
using Avalonia.VisualTree; |
|||
|
|||
namespace ControlCatalog.Pages; |
|||
|
|||
public class PointersPage : Decorator |
|||
{ |
|||
public PointersPage() |
|||
{ |
|||
Child = new TabControl |
|||
{ |
|||
Items = new[] |
|||
{ |
|||
new TabItem() { Header = "Contacts", Content = new PointerContactsTab() }, |
|||
new TabItem() { Header = "IntermediatePoints", Content = new PointerIntermediatePointsTab() }, |
|||
new TabItem() { Header = "Pressure", Content = new PointerPressureTab() } |
|||
} |
|||
}; |
|||
} |
|||
|
|||
|
|||
class PointerContactsTab : Control |
|||
{ |
|||
class PointerInfo |
|||
{ |
|||
public Point Point { get; set; } |
|||
public Color Color { get; set; } |
|||
} |
|||
|
|||
private static Color[] AllColors = new[] |
|||
{ |
|||
Colors.Aqua, |
|||
Colors.Beige, |
|||
Colors.Chartreuse, |
|||
Colors.Coral, |
|||
Colors.Fuchsia, |
|||
Colors.Crimson, |
|||
Colors.Lavender, |
|||
Colors.Orange, |
|||
Colors.Orchid, |
|||
Colors.ForestGreen, |
|||
Colors.SteelBlue, |
|||
Colors.PapayaWhip, |
|||
Colors.PaleVioletRed, |
|||
Colors.Goldenrod, |
|||
Colors.Maroon, |
|||
Colors.Moccasin, |
|||
Colors.Navy, |
|||
Colors.Wheat, |
|||
Colors.Violet, |
|||
Colors.Sienna, |
|||
Colors.Indigo, |
|||
Colors.Honeydew |
|||
}; |
|||
|
|||
private Dictionary<IPointer, PointerInfo> _pointers = new Dictionary<IPointer, PointerInfo>(); |
|||
|
|||
public PointerContactsTab() |
|||
{ |
|||
ClipToBounds = true; |
|||
} |
|||
|
|||
void UpdatePointer(PointerEventArgs e) |
|||
{ |
|||
if (!_pointers.TryGetValue(e.Pointer, out var info)) |
|||
{ |
|||
if (e.RoutedEvent == PointerMovedEvent) |
|||
return; |
|||
var colors = AllColors.Except(_pointers.Values.Select(c => c.Color)).ToArray(); |
|||
var color = colors[new Random().Next(0, colors.Length - 1)]; |
|||
_pointers[e.Pointer] = info = new PointerInfo {Color = color}; |
|||
} |
|||
|
|||
info.Point = e.GetPosition(this); |
|||
InvalidateVisual(); |
|||
} |
|||
|
|||
protected override void OnPointerPressed(PointerPressedEventArgs e) |
|||
{ |
|||
UpdatePointer(e); |
|||
e.Pointer.Capture(this); |
|||
e.Handled = true; |
|||
base.OnPointerPressed(e); |
|||
} |
|||
|
|||
protected override void OnPointerMoved(PointerEventArgs e) |
|||
{ |
|||
UpdatePointer(e); |
|||
e.Handled = true; |
|||
base.OnPointerMoved(e); |
|||
} |
|||
|
|||
protected override void OnPointerReleased(PointerReleasedEventArgs e) |
|||
{ |
|||
_pointers.Remove(e.Pointer); |
|||
e.Handled = true; |
|||
InvalidateVisual(); |
|||
} |
|||
|
|||
protected override void OnPointerCaptureLost(PointerCaptureLostEventArgs e) |
|||
{ |
|||
_pointers.Remove(e.Pointer); |
|||
InvalidateVisual(); |
|||
} |
|||
|
|||
public override void Render(DrawingContext context) |
|||
{ |
|||
context.FillRectangle(Brushes.Transparent, new Rect(default, Bounds.Size)); |
|||
foreach (var pt in _pointers.Values) |
|||
{ |
|||
var brush = new ImmutableSolidColorBrush(pt.Color); |
|||
|
|||
context.DrawEllipse(brush, null, pt.Point, 75, 75); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public class PointerIntermediatePointsTab : Decorator |
|||
{ |
|||
public PointerIntermediatePointsTab() |
|||
{ |
|||
this[TextElement.ForegroundProperty] = Brushes.Black; |
|||
var slider = new Slider |
|||
{ |
|||
Margin = new Thickness(5), |
|||
Minimum = 0, |
|||
Maximum = 500 |
|||
}; |
|||
|
|||
var status = new TextBlock() |
|||
{ |
|||
HorizontalAlignment = HorizontalAlignment.Left, |
|||
VerticalAlignment = VerticalAlignment.Top, |
|||
}; |
|||
Child = new Grid |
|||
{ |
|||
Children = |
|||
{ |
|||
new PointerCanvas(slider, status, true), |
|||
new Border |
|||
{ |
|||
Background = Brushes.LightYellow, |
|||
Child = new StackPanel |
|||
{ |
|||
Children = |
|||
{ |
|||
new StackPanel |
|||
{ |
|||
Orientation = Orientation.Horizontal, |
|||
Children = |
|||
{ |
|||
new TextBlock { Text = "Thread sleep:" }, |
|||
new TextBlock() |
|||
{ |
|||
[!TextBlock.TextProperty] =slider.GetObservable(Slider.ValueProperty) |
|||
.Select(x=>x.ToString()).ToBinding() |
|||
} |
|||
} |
|||
}, |
|||
slider |
|||
} |
|||
}, |
|||
|
|||
HorizontalAlignment = HorizontalAlignment.Right, |
|||
VerticalAlignment = VerticalAlignment.Top, |
|||
Width = 300, |
|||
Height = 60 |
|||
}, |
|||
status |
|||
} |
|||
}; |
|||
} |
|||
} |
|||
|
|||
public class PointerPressureTab : Decorator |
|||
{ |
|||
public PointerPressureTab() |
|||
{ |
|||
this[TextBlock.ForegroundProperty] = Brushes.Black; |
|||
|
|||
var status = new TextBlock() |
|||
{ |
|||
HorizontalAlignment = HorizontalAlignment.Left, |
|||
VerticalAlignment = VerticalAlignment.Top, |
|||
FontSize = 12 |
|||
}; |
|||
Child = new Grid |
|||
{ |
|||
Children = |
|||
{ |
|||
new PointerCanvas(null, status, false), |
|||
status |
|||
} |
|||
}; |
|||
} |
|||
} |
|||
|
|||
class PointerCanvas : Control |
|||
{ |
|||
private readonly Slider? _slider; |
|||
private readonly TextBlock _status; |
|||
private readonly bool _drawPoints; |
|||
private int _events; |
|||
private Stopwatch _stopwatch = Stopwatch.StartNew(); |
|||
private IDisposable? _statusUpdated; |
|||
private Dictionary<int, PointerPoints> _pointers = new(); |
|||
private PointerPointProperties? _lastProperties; |
|||
class PointerPoints |
|||
{ |
|||
struct CanvasPoint |
|||
{ |
|||
public IBrush Brush; |
|||
public Point Point; |
|||
public double Radius; |
|||
public double Pressure; |
|||
} |
|||
|
|||
readonly CanvasPoint[] _points = new CanvasPoint[1000]; |
|||
int _index; |
|||
|
|||
public void Render(DrawingContext context, bool drawPoints) |
|||
{ |
|||
|
|||
CanvasPoint? prev = null; |
|||
for (var c = 0; c < _points.Length; c++) |
|||
{ |
|||
var i = (c + _index) % _points.Length; |
|||
var pt = _points[i]; |
|||
var thickness = pt.Pressure == 0 ? 1 : (pt.Pressure / 1024) * 5; |
|||
|
|||
if (drawPoints) |
|||
{ |
|||
if (prev.HasValue && prev.Value.Brush != null && pt.Brush != null) |
|||
context.DrawLine(new Pen(Brushes.Black, thickness), prev.Value.Point, pt.Point); |
|||
if (pt.Brush != null) |
|||
context.DrawEllipse(pt.Brush, null, pt.Point, pt.Radius, pt.Radius); |
|||
} |
|||
else |
|||
{ |
|||
if (prev.HasValue && prev.Value.Brush != null && pt.Brush != null) |
|||
context.DrawLine(new Pen(Brushes.Black, thickness, lineCap: PenLineCap.Round, lineJoin: PenLineJoin.Round), prev.Value.Point, pt.Point); |
|||
} |
|||
prev = pt; |
|||
} |
|||
|
|||
} |
|||
|
|||
void AddPoint(Point pt, IBrush brush, double radius, float pressure) |
|||
{ |
|||
_points[_index] = new CanvasPoint { Point = pt, Brush = brush, Radius = radius, Pressure = pressure }; |
|||
_index = (_index + 1) % _points.Length; |
|||
} |
|||
|
|||
public void HandleEvent(PointerEventArgs e, Visual v) |
|||
{ |
|||
e.Handled = true; |
|||
var currentPoint = e.GetCurrentPoint(v); |
|||
if (e.RoutedEvent == PointerPressedEvent) |
|||
AddPoint(currentPoint.Position, Brushes.Green, 10, currentPoint.Properties.Pressure); |
|||
else if (e.RoutedEvent == PointerReleasedEvent) |
|||
AddPoint(currentPoint.Position, Brushes.Red, 10, currentPoint.Properties.Pressure); |
|||
else |
|||
{ |
|||
var pts = e.GetIntermediatePoints(v); |
|||
for (var c = 0; c < pts.Count; c++) |
|||
{ |
|||
var pt = pts[c]; |
|||
AddPoint(pt.Position, c == pts.Count - 1 ? Brushes.Blue : Brushes.Black, |
|||
c == pts.Count - 1 ? 5 : 2, pt.Properties.Pressure); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
public PointerCanvas(Slider? slider, TextBlock status, bool drawPoints) |
|||
{ |
|||
_slider = slider; |
|||
_status = status; |
|||
_drawPoints = drawPoints; |
|||
} |
|||
|
|||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) |
|||
{ |
|||
base.OnAttachedToVisualTree(e); |
|||
|
|||
_statusUpdated = DispatcherTimer.Run(() => |
|||
{ |
|||
if (_stopwatch.Elapsed.TotalSeconds > 1) |
|||
{ |
|||
_status.Text = $@"Events per second: {(_events / _stopwatch.Elapsed.TotalSeconds)}
|
|||
PointerUpdateKind: {_lastProperties?.PointerUpdateKind} |
|||
IsLeftButtonPressed: {_lastProperties?.IsLeftButtonPressed} |
|||
IsRightButtonPressed: {_lastProperties?.IsRightButtonPressed} |
|||
IsMiddleButtonPressed: {_lastProperties?.IsMiddleButtonPressed} |
|||
IsXButton1Pressed: {_lastProperties?.IsXButton1Pressed} |
|||
IsXButton2Pressed: {_lastProperties?.IsXButton2Pressed} |
|||
IsBarrelButtonPressed: {_lastProperties?.IsBarrelButtonPressed} |
|||
IsEraser: {_lastProperties?.IsEraser} |
|||
IsInverted: {_lastProperties?.IsInverted} |
|||
Pressure: {_lastProperties?.Pressure} |
|||
XTilt: {_lastProperties?.XTilt} |
|||
YTilt: {_lastProperties?.YTilt} |
|||
Twist: {_lastProperties?.Twist}";
|
|||
_stopwatch.Restart(); |
|||
_events = 0; |
|||
} |
|||
|
|||
return true; |
|||
}, TimeSpan.FromMilliseconds(10)); |
|||
} |
|||
|
|||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) |
|||
{ |
|||
base.OnDetachedFromVisualTree(e); |
|||
|
|||
_statusUpdated?.Dispose(); |
|||
} |
|||
|
|||
void HandleEvent(PointerEventArgs e) |
|||
{ |
|||
_events++; |
|||
if (_slider != null) |
|||
{ |
|||
Thread.Sleep((int)_slider.Value); |
|||
} |
|||
InvalidateVisual(); |
|||
|
|||
if (e.RoutedEvent == PointerReleasedEvent && e.Pointer.Type == PointerType.Touch) |
|||
{ |
|||
_pointers.Remove(e.Pointer.Id); |
|||
return; |
|||
} |
|||
|
|||
var lastPointer = e.GetCurrentPoint(this); |
|||
_lastProperties = lastPointer.Properties; |
|||
|
|||
if (e.Pointer.Type != PointerType.Pen |
|||
|| lastPointer.Properties.Pressure > 0) |
|||
{ |
|||
if (!_pointers.TryGetValue(e.Pointer.Id, out var pt)) |
|||
_pointers[e.Pointer.Id] = pt = new PointerPoints(); |
|||
pt.HandleEvent(e, this); |
|||
} |
|||
} |
|||
|
|||
public override void Render(DrawingContext context) |
|||
{ |
|||
context.FillRectangle(Brushes.White, Bounds); |
|||
foreach (var pt in _pointers.Values) |
|||
pt.Render(context, _drawPoints); |
|||
base.Render(context); |
|||
} |
|||
|
|||
protected override void OnPointerPressed(PointerPressedEventArgs e) |
|||
{ |
|||
if (e.ClickCount == 2) |
|||
{ |
|||
_pointers.Clear(); |
|||
InvalidateVisual(); |
|||
return; |
|||
} |
|||
|
|||
HandleEvent(e); |
|||
base.OnPointerPressed(e); |
|||
} |
|||
|
|||
protected override void OnPointerMoved(PointerEventArgs e) |
|||
{ |
|||
HandleEvent(e); |
|||
base.OnPointerMoved(e); |
|||
} |
|||
|
|||
protected override void OnPointerReleased(PointerReleasedEventArgs e) |
|||
{ |
|||
HandleEvent(e); |
|||
base.OnPointerReleased(e); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,65 @@ |
|||
<UserControl x:Class="ControlCatalog.Pages.PointersPage" |
|||
xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|||
xmlns:local="using:ControlCatalog.Pages"> |
|||
<TabControl> |
|||
<TabItem Header="Contacts"> |
|||
<local:PointerContactsTab /> |
|||
</TabItem> |
|||
<TabItem Header="IntermediatePoints"> |
|||
<Panel TextElement.Foreground="Black"> |
|||
<local:PointerCanvas x:Name="IntermediatePointsCanvas" |
|||
DrawOnlyPoints="True" |
|||
Status="{Binding #Status1TextBlock.Text, Mode=OneWayToSource}" |
|||
ThreadSleep="{Binding #ThreadSleepSlider.Value}" /> |
|||
<Border Width="300" |
|||
Height="60" |
|||
HorizontalAlignment="Right" |
|||
VerticalAlignment="Top"> |
|||
<StackPanel Background="LightYellow"> |
|||
<TextBlock Text="{Binding #ThreadSleepSlider.Value, StringFormat='Thread sleep: {0} / 500'}" /> |
|||
<Slider x:Name="ThreadSleepSlider" |
|||
Maximum="500" |
|||
Minimum="0" /> |
|||
</StackPanel> |
|||
</Border> |
|||
<TextBlock x:Name="Status1TextBlock" |
|||
HorizontalAlignment="Left" |
|||
VerticalAlignment="Top" /> |
|||
</Panel> |
|||
</TabItem> |
|||
<TabItem Header="Pressure"> |
|||
<Panel TextElement.Foreground="Black"> |
|||
<local:PointerCanvas x:Name="PressureCanvas" |
|||
DrawOnlyPoints="False" |
|||
Status="{Binding #Status2TextBlock.Text, Mode=OneWayToSource}" |
|||
ThreadSleep="0" /> |
|||
<TextBlock x:Name="Status2TextBlock" |
|||
HorizontalAlignment="Left" |
|||
VerticalAlignment="Top" /> |
|||
</Panel> |
|||
</TabItem> |
|||
<TabItem Header="Capture"> |
|||
<WrapPanel> |
|||
<Border Name="BorderCapture1" |
|||
MinWidth="250" |
|||
MinHeight="170" |
|||
Margin="5" |
|||
Padding="50" |
|||
Background="{DynamicResource SystemAccentColor}" |
|||
ToolTip.Placement="Bottom"> |
|||
<TextBlock>Capture 1</TextBlock> |
|||
</Border> |
|||
<Border Name="BorderCapture2" |
|||
MinWidth="250" |
|||
MinHeight="170" |
|||
Margin="5" |
|||
Padding="50" |
|||
Background="{DynamicResource SystemAccentColor}" |
|||
ToolTip.Placement="Bottom"> |
|||
<TextBlock>Capture 2</TextBlock> |
|||
</Border> |
|||
</WrapPanel> |
|||
</TabItem> |
|||
</TabControl> |
|||
</UserControl> |
|||
@ -0,0 +1,76 @@ |
|||
using System; |
|||
using Avalonia; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Input; |
|||
using Avalonia.Interactivity; |
|||
using Avalonia.Markup.Xaml; |
|||
|
|||
namespace ControlCatalog.Pages; |
|||
|
|||
public class PointersPage : UserControl |
|||
{ |
|||
public PointersPage() |
|||
{ |
|||
this.InitializeComponent(); |
|||
|
|||
var border1 = this.Get<Border>("BorderCapture1"); |
|||
var border2 = this.Get<Border>("BorderCapture2"); |
|||
|
|||
border1.PointerPressed += Border_PointerPressed; |
|||
border1.PointerReleased += Border_PointerReleased; |
|||
border1.PointerCaptureLost += Border_PointerCaptureLost; |
|||
border1.PointerMoved += Border_PointerUpdated; |
|||
border1.PointerEnter += Border_PointerUpdated; |
|||
border1.PointerLeave += Border_PointerUpdated; |
|||
|
|||
border2.PointerPressed += Border_PointerPressed; |
|||
border2.PointerReleased += Border_PointerReleased; |
|||
border2.PointerCaptureLost += Border_PointerCaptureLost; |
|||
border2.PointerMoved += Border_PointerUpdated; |
|||
border2.PointerEnter += Border_PointerUpdated; |
|||
border2.PointerLeave += Border_PointerUpdated; |
|||
} |
|||
|
|||
private void Border_PointerUpdated(object sender, PointerEventArgs e) |
|||
{ |
|||
var textBlock = (TextBlock)((Border)sender).Child; |
|||
var position = e.GetPosition((Border)sender); |
|||
textBlock.Text = @$"Captured: {e.Pointer.Captured == sender}
|
|||
PointerId: {e.Pointer.Id} |
|||
Position: {(int)position.X} {(int)position.Y}";
|
|||
e.Handled = true; |
|||
} |
|||
|
|||
private void Border_PointerCaptureLost(object sender, PointerCaptureLostEventArgs e) |
|||
{ |
|||
var textBlock = (TextBlock)((Border)sender).Child; |
|||
textBlock.Text = @$"Captured: {e.Pointer.Captured == sender}
|
|||
PointerId: {e.Pointer.Id} |
|||
Position: ??? ???";
|
|||
e.Handled = true; |
|||
} |
|||
|
|||
private void Border_PointerReleased(object sender, PointerReleasedEventArgs e) |
|||
{ |
|||
if (e.Pointer.Captured == sender) |
|||
{ |
|||
e.Pointer.Capture(null); |
|||
e.Handled = true; |
|||
} |
|||
else |
|||
{ |
|||
throw new InvalidOperationException("How?"); |
|||
} |
|||
} |
|||
|
|||
private void Border_PointerPressed(object sender, PointerPressedEventArgs e) |
|||
{ |
|||
e.Pointer.Capture((Border)sender); |
|||
e.Handled = true; |
|||
} |
|||
|
|||
private void InitializeComponent() |
|||
{ |
|||
AvaloniaXamlLoader.Load(this); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue