committed by
GitHub
51 changed files with 2176 additions and 516 deletions
@ -0,0 +1,235 @@ |
|||
#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; |
|||
private PointerUpdateKind? _lastNonOtherUpdateKind; |
|||
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.TotalMilliseconds > 250) |
|||
{ |
|||
Status = $@"Events per second: {(_events / _stopwatch.Elapsed.TotalSeconds)}
|
|||
PointerUpdateKind: {_lastProperties?.PointerUpdateKind} |
|||
Last PointerUpdateKind != Other: {_lastNonOtherUpdateKind} |
|||
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(); |
|||
|
|||
var lastPointer = e.GetCurrentPoint(this); |
|||
_lastProperties = lastPointer.Properties; |
|||
|
|||
if (_lastProperties.PointerUpdateKind != PointerUpdateKind.Other) |
|||
{ |
|||
_lastNonOtherUpdateKind = _lastProperties.PointerUpdateKind; |
|||
} |
|||
|
|||
if (e.RoutedEvent == PointerReleasedEvent && e.Pointer.Type == PointerType.Touch) |
|||
{ |
|||
_pointers.Remove(e.Pointer.Id); |
|||
return; |
|||
} |
|||
|
|||
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); |
|||
} |
|||
|
|||
protected override void OnPointerCaptureLost(PointerCaptureLostEventArgs e) |
|||
{ |
|||
_lastProperties = null; |
|||
base.OnPointerCaptureLost(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,322 +0,0 @@ |
|||
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() } |
|||
} |
|||
}; |
|||
} |
|||
|
|||
|
|||
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), |
|||
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 |
|||
} |
|||
}; |
|||
} |
|||
|
|||
class PointerCanvas : Control |
|||
{ |
|||
private readonly Slider _slider; |
|||
private readonly TextBlock _status; |
|||
private int _events; |
|||
private Stopwatch _stopwatch = Stopwatch.StartNew(); |
|||
private Dictionary<int, PointerPoints> _pointers = new(); |
|||
class PointerPoints |
|||
{ |
|||
struct CanvasPoint |
|||
{ |
|||
public IBrush Brush; |
|||
public Point Point; |
|||
public double Radius; |
|||
} |
|||
|
|||
readonly CanvasPoint[] _points = new CanvasPoint[1000]; |
|||
int _index; |
|||
|
|||
public void Render(DrawingContext context) |
|||
{ |
|||
|
|||
CanvasPoint? prev = null; |
|||
for (var c = 0; c < _points.Length; c++) |
|||
{ |
|||
var i = (c + _index) % _points.Length; |
|||
var pt = _points[i]; |
|||
if (prev.HasValue && prev.Value.Brush != null && pt.Brush != null) |
|||
context.DrawLine(new Pen(Brushes.Black), prev.Value.Point, pt.Point); |
|||
prev = pt; |
|||
if (pt.Brush != null) |
|||
context.DrawEllipse(pt.Brush, null, pt.Point, pt.Radius, pt.Radius); |
|||
|
|||
} |
|||
|
|||
} |
|||
|
|||
void AddPoint(Point pt, IBrush brush, double radius) |
|||
{ |
|||
_points[_index] = new CanvasPoint { Point = pt, Brush = brush, Radius = radius }; |
|||
_index = (_index + 1) % _points.Length; |
|||
} |
|||
|
|||
public void HandleEvent(PointerEventArgs e, Visual v) |
|||
{ |
|||
e.Handled = true; |
|||
if (e.RoutedEvent == PointerPressedEvent) |
|||
AddPoint(e.GetPosition(v), Brushes.Green, 10); |
|||
else if (e.RoutedEvent == PointerReleasedEvent) |
|||
AddPoint(e.GetPosition(v), 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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
public PointerCanvas(Slider slider, TextBlock status) |
|||
{ |
|||
_slider = slider; |
|||
_status = status; |
|||
DispatcherTimer.Run(() => |
|||
{ |
|||
if (_stopwatch.Elapsed.TotalSeconds > 1) |
|||
{ |
|||
_status.Text = "Events per second: " + (_events / _stopwatch.Elapsed.TotalSeconds); |
|||
_stopwatch.Restart(); |
|||
_events = 0; |
|||
} |
|||
|
|||
return this.GetVisualRoot() != null; |
|||
}, TimeSpan.FromMilliseconds(10)); |
|||
} |
|||
|
|||
|
|||
void HandleEvent(PointerEventArgs e) |
|||
{ |
|||
_events++; |
|||
Thread.Sleep((int)_slider.Value); |
|||
InvalidateVisual(); |
|||
|
|||
if (e.RoutedEvent == PointerReleasedEvent && e.Pointer.Type == PointerType.Touch) |
|||
{ |
|||
_pointers.Remove(e.Pointer.Id); |
|||
return; |
|||
} |
|||
|
|||
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); |
|||
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,66 @@ |
|||
<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" |
|||
Value="50" |
|||
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,78 @@ |
|||
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.PointerEntered += Border_PointerUpdated; |
|||
border1.PointerExited += Border_PointerUpdated; |
|||
|
|||
border2.PointerPressed += Border_PointerPressed; |
|||
border2.PointerReleased += Border_PointerReleased; |
|||
border2.PointerCaptureLost += Border_PointerCaptureLost; |
|||
border2.PointerMoved += Border_PointerUpdated; |
|||
border2.PointerEntered += Border_PointerUpdated; |
|||
border2.PointerExited += Border_PointerUpdated; |
|||
} |
|||
|
|||
private void Border_PointerUpdated(object sender, PointerEventArgs e) |
|||
{ |
|||
var textBlock = (TextBlock)((Border)sender).Child; |
|||
var position = e.GetPosition((Border)sender); |
|||
textBlock.Text = @$"Type: {e.Pointer.Type}
|
|||
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 = @$"Type: {e.Pointer.Type}
|
|||
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); |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
namespace Avalonia.Input |
|||
{ |
|||
/// <summary>
|
|||
/// Represents a pen/stylus device.
|
|||
/// </summary>
|
|||
public interface IPenDevice : IPointerDevice |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,174 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Reactive.Linq; |
|||
using Avalonia.Input.Raw; |
|||
using Avalonia.Platform; |
|||
using Avalonia.VisualTree; |
|||
|
|||
namespace Avalonia.Input |
|||
{ |
|||
/// <summary>
|
|||
/// Represents a pen/stylus device.
|
|||
/// </summary>
|
|||
public class PenDevice : IPenDevice, IDisposable |
|||
{ |
|||
private readonly Dictionary<long, Pointer> _pointers = new(); |
|||
private readonly Dictionary<long, PixelPoint> _lastPositions = new(); |
|||
private int _clickCount; |
|||
private Rect _lastClickRect; |
|||
private ulong _lastClickTime; |
|||
private MouseButton _lastMouseDownButton; |
|||
|
|||
private bool _disposed; |
|||
|
|||
public void ProcessRawEvent(RawInputEventArgs e) |
|||
{ |
|||
if (!e.Handled && e is RawPointerEventArgs margs) |
|||
ProcessRawEvent(margs); |
|||
} |
|||
|
|||
private void ProcessRawEvent(RawPointerEventArgs e) |
|||
{ |
|||
e = e ?? throw new ArgumentNullException(nameof(e)); |
|||
|
|||
if (!_pointers.TryGetValue(e.RawPointerId, out var pointer)) |
|||
{ |
|||
if (e.Type == RawPointerEventType.LeftButtonUp |
|||
|| e.Type == RawPointerEventType.TouchEnd) |
|||
return; |
|||
|
|||
_pointers[e.RawPointerId] = pointer = new Pointer(Pointer.GetNextFreeId(), |
|||
PointerType.Pen, _pointers.Count == 0); |
|||
} |
|||
|
|||
_lastPositions[e.RawPointerId] = e.Root.PointToScreen(e.Position); |
|||
|
|||
var props = new PointerPointProperties(e.InputModifiers, e.Type.ToUpdateKind(), |
|||
e.Point.Twist, e.Point.Pressure, e.Point.XTilt, e.Point.YTilt); |
|||
var keyModifiers = e.InputModifiers.ToKeyModifiers(); |
|||
|
|||
bool shouldReleasePointer = false; |
|||
switch (e.Type) |
|||
{ |
|||
case RawPointerEventType.LeaveWindow: |
|||
shouldReleasePointer = true; |
|||
break; |
|||
case RawPointerEventType.LeftButtonDown: |
|||
e.Handled = PenDown(pointer, e.Timestamp, e.Root, e.Position, props, keyModifiers, e.InputHitTestResult); |
|||
break; |
|||
case RawPointerEventType.LeftButtonUp: |
|||
e.Handled = PenUp(pointer, e.Timestamp, e.Root, e.Position, props, keyModifiers, e.InputHitTestResult); |
|||
break; |
|||
case RawPointerEventType.Move: |
|||
e.Handled = PenMove(pointer, e.Timestamp, e.Root, e.Position, props, keyModifiers, e.InputHitTestResult, e.IntermediatePoints); |
|||
break; |
|||
} |
|||
|
|||
if (shouldReleasePointer) |
|||
{ |
|||
pointer.Dispose(); |
|||
_pointers.Remove(e.RawPointerId); |
|||
_lastPositions.Remove(e.RawPointerId); |
|||
} |
|||
} |
|||
|
|||
private bool PenDown(Pointer pointer, ulong timestamp, |
|||
IInputElement root, Point p, PointerPointProperties properties, |
|||
KeyModifiers inputModifiers, IInputElement? hitTest) |
|||
{ |
|||
var source = pointer.Captured ?? hitTest; |
|||
|
|||
if (source != null) |
|||
{ |
|||
pointer.Capture(source); |
|||
var settings = AvaloniaLocator.Current.GetService<IPlatformSettings>(); |
|||
var doubleClickTime = settings?.DoubleClickTime.TotalMilliseconds ?? 500; |
|||
var doubleClickSize = settings?.DoubleClickSize ?? new Size(4, 4); |
|||
|
|||
if (!_lastClickRect.Contains(p) || timestamp - _lastClickTime > doubleClickTime) |
|||
{ |
|||
_clickCount = 0; |
|||
} |
|||
|
|||
++_clickCount; |
|||
_lastClickTime = timestamp; |
|||
_lastClickRect = new Rect(p, new Size()) |
|||
.Inflate(new Thickness(doubleClickSize.Width / 2, doubleClickSize.Height / 2)); |
|||
_lastMouseDownButton = properties.PointerUpdateKind.GetMouseButton(); |
|||
var e = new PointerPressedEventArgs(source, pointer, root, p, timestamp, properties, inputModifiers, _clickCount); |
|||
source.RaiseEvent(e); |
|||
return e.Handled; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
private bool PenMove(Pointer pointer, ulong timestamp, |
|||
IInputRoot root, Point p, PointerPointProperties properties, |
|||
KeyModifiers inputModifiers, IInputElement? hitTest, |
|||
Lazy<IReadOnlyList<RawPointerPoint>?>? intermediatePoints) |
|||
{ |
|||
var source = pointer.Captured ?? hitTest; |
|||
|
|||
if (source is not null) |
|||
{ |
|||
var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, pointer, root, |
|||
p, timestamp, properties, inputModifiers, intermediatePoints); |
|||
|
|||
source.RaiseEvent(e); |
|||
return e.Handled; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
private bool PenUp(Pointer pointer, ulong timestamp, |
|||
IInputElement root, Point p, PointerPointProperties properties, |
|||
KeyModifiers inputModifiers, IInputElement? hitTest) |
|||
{ |
|||
var source = pointer.Captured ?? hitTest; |
|||
|
|||
if (source is not null) |
|||
{ |
|||
var e = new PointerReleasedEventArgs(source, pointer, root, p, timestamp, properties, inputModifiers, |
|||
_lastMouseDownButton); |
|||
|
|||
source?.RaiseEvent(e); |
|||
pointer.Capture(null); |
|||
return e.Handled; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
if (_disposed) |
|||
return; |
|||
var values = _pointers.Values.ToList(); |
|||
_pointers.Clear(); |
|||
_disposed = true; |
|||
foreach (var p in values) |
|||
p.Dispose(); |
|||
} |
|||
|
|||
[Obsolete] |
|||
IInputElement? IPointerDevice.Captured => _pointers.Values |
|||
.FirstOrDefault(p => p.IsPrimary)?.Captured; |
|||
|
|||
[Obsolete] |
|||
void IPointerDevice.Capture(IInputElement? control) => _pointers.Values |
|||
.FirstOrDefault(p => p.IsPrimary)?.Capture(control); |
|||
|
|||
[Obsolete] |
|||
Point IPointerDevice.GetPosition(IVisual relativeTo) => new Point(-1, -1); |
|||
|
|||
public IPointer? TryGetPointer(RawPointerEventArgs ev) |
|||
{ |
|||
return _pointers.TryGetValue(ev.RawPointerId, out var pointer) |
|||
? pointer |
|||
: null; |
|||
} |
|||
} |
|||
} |
|||
@ -1,15 +1,26 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Input.Raw |
|||
{ |
|||
public class RawTouchEventArgs : RawPointerEventArgs |
|||
{ |
|||
public RawTouchEventArgs(IInputDevice device, ulong timestamp, IInputRoot root, |
|||
RawPointerEventType type, Point position, RawInputModifiers inputModifiers, |
|||
long touchPointId) |
|||
long rawPointerId) |
|||
: base(device, timestamp, root, type, position, inputModifiers) |
|||
{ |
|||
TouchPointId = touchPointId; |
|||
RawPointerId = rawPointerId; |
|||
} |
|||
|
|||
public RawTouchEventArgs(IInputDevice device, ulong timestamp, IInputRoot root, |
|||
RawPointerEventType type, RawPointerPoint point, RawInputModifiers inputModifiers, |
|||
long rawPointerId) |
|||
: base(device, timestamp, root, type, point, inputModifiers) |
|||
{ |
|||
RawPointerId = rawPointerId; |
|||
} |
|||
|
|||
public long TouchPointId { get; set; } |
|||
[Obsolete("Use RawPointerId")] |
|||
public long TouchPointId { get => RawPointerId; set => RawPointerId = value; } |
|||
} |
|||
} |
|||
|
|||
@ -1 +1 @@ |
|||
Subproject commit daaac590e078967b78045f74c38ef046d00d8582 |
|||
Subproject commit a4e6be2d1407abec4f35fcb208848830ce513ead |
|||
@ -0,0 +1,12 @@ |
|||
namespace Avalonia.Markup.Xaml |
|||
{ |
|||
public interface IAddChild |
|||
{ |
|||
void AddChild(object child); |
|||
} |
|||
|
|||
public interface IAddChild<T> : IAddChild |
|||
{ |
|||
void AddChild(T child); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue