committed by
GitHub
109 changed files with 2740 additions and 903 deletions
@ -0,0 +1,99 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using Avalonia; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Input; |
|||
using Avalonia.Media; |
|||
using Avalonia.Media.Immutable; |
|||
|
|||
namespace ControlCatalog.Pages |
|||
{ |
|||
public class PointersPage : 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 PointersPage() |
|||
{ |
|||
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); |
|||
base.OnPointerPressed(e); |
|||
} |
|||
|
|||
protected override void OnPointerMoved(PointerEventArgs e) |
|||
{ |
|||
UpdatePointer(e); |
|||
base.OnPointerMoved(e); |
|||
} |
|||
|
|||
protected override void OnPointerReleased(PointerReleasedEventArgs 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.DrawGeometry(brush, null, new EllipseGeometry(new Rect(pt.Point.X - 75, pt.Point.Y - 75, |
|||
150, 150))); |
|||
} |
|||
|
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,49 @@ |
|||
using System.Diagnostics; |
|||
using Avalonia; |
|||
using Avalonia.Controls; |
|||
using Avalonia.LogicalTree; |
|||
using Avalonia.Media; |
|||
using Avalonia.Media.Imaging; |
|||
using Avalonia.Threading; |
|||
using Avalonia.Visuals.Media.Imaging; |
|||
|
|||
namespace RenderDemo.Pages |
|||
{ |
|||
public class RenderTargetBitmapPage : Control |
|||
{ |
|||
private RenderTargetBitmap _bitmap; |
|||
|
|||
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) |
|||
{ |
|||
_bitmap = new RenderTargetBitmap(new PixelSize(200, 200), new Vector(96, 96)); |
|||
base.OnAttachedToLogicalTree(e); |
|||
} |
|||
|
|||
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) |
|||
{ |
|||
_bitmap.Dispose(); |
|||
_bitmap = null; |
|||
base.OnDetachedFromLogicalTree(e); |
|||
} |
|||
|
|||
readonly Stopwatch _st = Stopwatch.StartNew(); |
|||
public override void Render(DrawingContext context) |
|||
{ |
|||
using (var ctxi = _bitmap.CreateDrawingContext(null)) |
|||
using(var ctx = new DrawingContext(ctxi, false)) |
|||
using (ctx.PushPostTransform(Matrix.CreateTranslation(-100, -100) |
|||
* Matrix.CreateRotation(_st.Elapsed.TotalSeconds) |
|||
* Matrix.CreateTranslation(100, 100))) |
|||
{ |
|||
ctxi.Clear(default); |
|||
ctx.FillRectangle(Brushes.Fuchsia, new Rect(50, 50, 100, 100)); |
|||
} |
|||
|
|||
context.DrawImage(_bitmap, 1, |
|||
new Rect(0, 0, 200, 200), |
|||
new Rect(0, 0, 200, 200)); |
|||
Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Background); |
|||
base.Render(context); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,127 @@ |
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Data; |
|||
using Avalonia.LogicalTree; |
|||
using Avalonia.Styling; |
|||
|
|||
namespace Avalonia.Input.GestureRecognizers |
|||
{ |
|||
public class GestureRecognizerCollection : IReadOnlyCollection<IGestureRecognizer>, IGestureRecognizerActionsDispatcher |
|||
{ |
|||
private readonly IInputElement _inputElement; |
|||
private List<IGestureRecognizer> _recognizers; |
|||
private Dictionary<IPointer, IGestureRecognizer> _pointerGrabs; |
|||
|
|||
|
|||
public GestureRecognizerCollection(IInputElement inputElement) |
|||
{ |
|||
_inputElement = inputElement; |
|||
} |
|||
|
|||
public void Add(IGestureRecognizer recognizer) |
|||
{ |
|||
if (_recognizers == null) |
|||
{ |
|||
// We initialize the collection when the first recognizer is added
|
|||
_recognizers = new List<IGestureRecognizer>(); |
|||
_pointerGrabs = new Dictionary<IPointer, IGestureRecognizer>(); |
|||
} |
|||
|
|||
_recognizers.Add(recognizer); |
|||
recognizer.Initialize(_inputElement, this); |
|||
|
|||
// Hacks to make bindings work
|
|||
|
|||
if (_inputElement is ILogical logicalParent && recognizer is ISetLogicalParent logical) |
|||
{ |
|||
logical.SetParent(logicalParent); |
|||
if (recognizer is IStyleable styleableRecognizer |
|||
&& _inputElement is IStyleable styleableParent) |
|||
styleableRecognizer.Bind(StyledElement.TemplatedParentProperty, |
|||
styleableParent.GetObservable(StyledElement.TemplatedParentProperty)); |
|||
} |
|||
} |
|||
|
|||
static readonly List<IGestureRecognizer> s_Empty = new List<IGestureRecognizer>(); |
|||
|
|||
public IEnumerator<IGestureRecognizer> GetEnumerator() |
|||
=> _recognizers?.GetEnumerator() ?? s_Empty.GetEnumerator(); |
|||
|
|||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); |
|||
|
|||
public int Count => _recognizers?.Count ?? 0; |
|||
|
|||
|
|||
internal bool HandlePointerPressed(PointerPressedEventArgs e) |
|||
{ |
|||
if (_recognizers == null) |
|||
return false; |
|||
foreach (var r in _recognizers) |
|||
{ |
|||
if(e.Handled) |
|||
break; |
|||
r.PointerPressed(e); |
|||
} |
|||
|
|||
return e.Handled; |
|||
} |
|||
|
|||
internal bool HandlePointerReleased(PointerReleasedEventArgs e) |
|||
{ |
|||
if (_recognizers == null) |
|||
return false; |
|||
if (_pointerGrabs.TryGetValue(e.Pointer, out var capture)) |
|||
{ |
|||
capture.PointerReleased(e); |
|||
} |
|||
else |
|||
foreach (var r in _recognizers) |
|||
{ |
|||
if (e.Handled) |
|||
break; |
|||
r.PointerReleased(e); |
|||
} |
|||
return e.Handled; |
|||
} |
|||
|
|||
internal bool HandlePointerMoved(PointerEventArgs e) |
|||
{ |
|||
if (_recognizers == null) |
|||
return false; |
|||
if (_pointerGrabs.TryGetValue(e.Pointer, out var capture)) |
|||
{ |
|||
capture.PointerMoved(e); |
|||
} |
|||
else |
|||
foreach (var r in _recognizers) |
|||
{ |
|||
if (e.Handled) |
|||
break; |
|||
r.PointerMoved(e); |
|||
} |
|||
return e.Handled; |
|||
} |
|||
|
|||
internal void HandlePointerCaptureLost(PointerCaptureLostEventArgs e) |
|||
{ |
|||
if (_recognizers == null) |
|||
return; |
|||
_pointerGrabs.Remove(e.Pointer); |
|||
foreach (var r in _recognizers) |
|||
{ |
|||
if(e.Handled) |
|||
break; |
|||
r.PointerCaptureLost(e); |
|||
} |
|||
} |
|||
|
|||
void IGestureRecognizerActionsDispatcher.Capture(IPointer pointer, IGestureRecognizer recognizer) |
|||
{ |
|||
pointer.Capture(_inputElement); |
|||
_pointerGrabs[pointer] = recognizer; |
|||
} |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
namespace Avalonia.Input.GestureRecognizers |
|||
{ |
|||
public interface IGestureRecognizer |
|||
{ |
|||
void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions); |
|||
void PointerPressed(PointerPressedEventArgs e); |
|||
void PointerReleased(PointerReleasedEventArgs e); |
|||
void PointerMoved(PointerEventArgs e); |
|||
void PointerCaptureLost(PointerCaptureLostEventArgs e); |
|||
} |
|||
|
|||
public interface IGestureRecognizerActionsDispatcher |
|||
{ |
|||
void Capture(IPointer pointer, IGestureRecognizer recognizer); |
|||
} |
|||
|
|||
public enum GestureRecognizerResult |
|||
{ |
|||
None, |
|||
Capture, |
|||
ReleaseCapture |
|||
} |
|||
} |
|||
@ -0,0 +1,183 @@ |
|||
using System; |
|||
using System.Diagnostics; |
|||
using Avalonia.Interactivity; |
|||
using Avalonia.Threading; |
|||
|
|||
namespace Avalonia.Input.GestureRecognizers |
|||
{ |
|||
public class ScrollGestureRecognizer |
|||
: StyledElement, // It's not an "element" in any way, shape or form, but TemplateBinding refuse to work otherwise
|
|||
IGestureRecognizer |
|||
{ |
|||
private bool _scrolling; |
|||
private Point _trackedRootPoint; |
|||
private IPointer _tracking; |
|||
private IInputElement _target; |
|||
private IGestureRecognizerActionsDispatcher _actions; |
|||
private bool _canHorizontallyScroll; |
|||
private bool _canVerticallyScroll; |
|||
private int _gestureId; |
|||
|
|||
// Movement per second
|
|||
private Vector _inertia; |
|||
private ulong? _lastMoveTimestamp; |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="CanHorizontallyScroll"/> property.
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<ScrollGestureRecognizer, bool> CanHorizontallyScrollProperty = |
|||
AvaloniaProperty.RegisterDirect<ScrollGestureRecognizer, bool>( |
|||
nameof(CanHorizontallyScroll), |
|||
o => o.CanHorizontallyScroll, |
|||
(o, v) => o.CanHorizontallyScroll = v); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="CanVerticallyScroll"/> property.
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<ScrollGestureRecognizer, bool> CanVerticallyScrollProperty = |
|||
AvaloniaProperty.RegisterDirect<ScrollGestureRecognizer, bool>( |
|||
nameof(CanVerticallyScroll), |
|||
o => o.CanVerticallyScroll, |
|||
(o, v) => o.CanVerticallyScroll = v); |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether the content can be scrolled horizontally.
|
|||
/// </summary>
|
|||
public bool CanHorizontallyScroll |
|||
{ |
|||
get => _canHorizontallyScroll; |
|||
set => SetAndRaise(CanHorizontallyScrollProperty, ref _canHorizontallyScroll, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating whether the content can be scrolled horizontally.
|
|||
/// </summary>
|
|||
public bool CanVerticallyScroll |
|||
{ |
|||
get => _canVerticallyScroll; |
|||
set => SetAndRaise(CanVerticallyScrollProperty, ref _canVerticallyScroll, value); |
|||
} |
|||
|
|||
|
|||
public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions) |
|||
{ |
|||
_target = target; |
|||
_actions = actions; |
|||
} |
|||
|
|||
public void PointerPressed(PointerPressedEventArgs e) |
|||
{ |
|||
if (e.Pointer.IsPrimary && e.Pointer.Type == PointerType.Touch) |
|||
{ |
|||
EndGesture(); |
|||
_tracking = e.Pointer; |
|||
_gestureId = ScrollGestureEventArgs.GetNextFreeId();; |
|||
_trackedRootPoint = e.GetPosition(null); |
|||
} |
|||
} |
|||
|
|||
// Arbitrary chosen value, probably need to move that to platform settings or something
|
|||
private const double ScrollStartDistance = 30; |
|||
|
|||
// Pixels per second speed that is considered to be the stop of inertiall scroll
|
|||
private const double InertialScrollSpeedEnd = 5; |
|||
|
|||
public void PointerMoved(PointerEventArgs e) |
|||
{ |
|||
if (e.Pointer == _tracking) |
|||
{ |
|||
var rootPoint = e.GetPosition(null); |
|||
if (!_scrolling) |
|||
{ |
|||
if (CanHorizontallyScroll && Math.Abs(_trackedRootPoint.X - rootPoint.X) > ScrollStartDistance) |
|||
_scrolling = true; |
|||
if (CanVerticallyScroll && Math.Abs(_trackedRootPoint.Y - rootPoint.Y) > ScrollStartDistance) |
|||
_scrolling = true; |
|||
if (_scrolling) |
|||
{ |
|||
_actions.Capture(e.Pointer, this); |
|||
} |
|||
} |
|||
|
|||
if (_scrolling) |
|||
{ |
|||
var vector = _trackedRootPoint - rootPoint; |
|||
var elapsed = _lastMoveTimestamp.HasValue ? |
|||
TimeSpan.FromMilliseconds(e.Timestamp - _lastMoveTimestamp.Value) : |
|||
TimeSpan.Zero; |
|||
|
|||
_lastMoveTimestamp = e.Timestamp; |
|||
_trackedRootPoint = rootPoint; |
|||
if (elapsed.TotalSeconds > 0) |
|||
_inertia = vector / elapsed.TotalSeconds; |
|||
_target.RaiseEvent(new ScrollGestureEventArgs(_gestureId, vector)); |
|||
e.Handled = true; |
|||
} |
|||
} |
|||
} |
|||
|
|||
public void PointerCaptureLost(PointerCaptureLostEventArgs e) |
|||
{ |
|||
if (e.Pointer == _tracking) EndGesture(); |
|||
} |
|||
|
|||
void EndGesture() |
|||
{ |
|||
_tracking = null; |
|||
if (_scrolling) |
|||
{ |
|||
_inertia = default; |
|||
_scrolling = false; |
|||
_target.RaiseEvent(new ScrollGestureEndedEventArgs(_gestureId)); |
|||
_gestureId = 0; |
|||
_lastMoveTimestamp = null; |
|||
} |
|||
|
|||
} |
|||
|
|||
|
|||
public void PointerReleased(PointerReleasedEventArgs e) |
|||
{ |
|||
if (e.Pointer == _tracking && _scrolling) |
|||
{ |
|||
e.Handled = true; |
|||
if (_inertia == default |
|||
|| e.Timestamp == 0 |
|||
|| _lastMoveTimestamp == 0 |
|||
|| e.Timestamp - _lastMoveTimestamp > 200) |
|||
EndGesture(); |
|||
else |
|||
{ |
|||
var savedGestureId = _gestureId; |
|||
var st = Stopwatch.StartNew(); |
|||
var lastTime = TimeSpan.Zero; |
|||
DispatcherTimer.Run(() => |
|||
{ |
|||
// Another gesture has started, finish the current one
|
|||
if (_gestureId != savedGestureId) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
var elapsedSinceLastTick = st.Elapsed - lastTime; |
|||
lastTime = st.Elapsed; |
|||
|
|||
var speed = _inertia * Math.Pow(0.15, st.Elapsed.TotalSeconds); |
|||
var distance = speed * elapsedSinceLastTick.TotalSeconds; |
|||
_target.RaiseEvent(new ScrollGestureEventArgs(_gestureId, distance)); |
|||
|
|||
|
|||
|
|||
if (Math.Abs(speed.X) < InertialScrollSpeedEnd || Math.Abs(speed.Y) <= InertialScrollSpeedEnd) |
|||
{ |
|||
EndGesture(); |
|||
return false; |
|||
} |
|||
|
|||
return true; |
|||
}, TimeSpan.FromMilliseconds(16), DispatcherPriority.Background); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
namespace Avalonia.Input |
|||
{ |
|||
public interface IPointer |
|||
{ |
|||
int Id { get; } |
|||
void Capture(IInputElement control); |
|||
IInputElement Captured { get; } |
|||
PointerType Type { get; } |
|||
bool IsPrimary { get; } |
|||
|
|||
} |
|||
|
|||
public enum PointerType |
|||
{ |
|||
Mouse, |
|||
Touch |
|||
} |
|||
} |
|||
@ -1,18 +1,20 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using Avalonia.VisualTree; |
|||
|
|||
namespace Avalonia.Input |
|||
{ |
|||
public interface IPointerDevice : IInputDevice |
|||
{ |
|||
[Obsolete("Use IPointer")] |
|||
IInputElement Captured { get; } |
|||
|
|||
|
|||
[Obsolete("Use IPointer")] |
|||
void Capture(IInputElement control); |
|||
|
|||
[Obsolete("Use PointerEventArgs.GetPosition")] |
|||
Point GetPosition(IVisual relativeTo); |
|||
|
|||
void SceneInvalidated(IInputRoot root, Rect rect); |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,67 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using Avalonia.Interactivity; |
|||
using Avalonia.VisualTree; |
|||
|
|||
namespace Avalonia.Input |
|||
{ |
|||
public class Pointer : IPointer, IDisposable |
|||
{ |
|||
private static int s_NextFreePointerId = 1000; |
|||
public static int GetNextFreeId() => s_NextFreePointerId++; |
|||
|
|||
public Pointer(int id, PointerType type, bool isPrimary) |
|||
{ |
|||
Id = id; |
|||
Type = type; |
|||
IsPrimary = isPrimary; |
|||
} |
|||
|
|||
public int Id { get; } |
|||
|
|||
IInputElement FindCommonParent(IInputElement control1, IInputElement control2) |
|||
{ |
|||
if (control1 == null || control2 == null) |
|||
return null; |
|||
var seen = new HashSet<IInputElement>(control1.GetSelfAndVisualAncestors().OfType<IInputElement>()); |
|||
return control2.GetSelfAndVisualAncestors().OfType<IInputElement>().FirstOrDefault(seen.Contains); |
|||
} |
|||
|
|||
public void Capture(IInputElement control) |
|||
{ |
|||
if (Captured != null) |
|||
Captured.DetachedFromVisualTree -= OnCaptureDetached; |
|||
var oldCapture = control; |
|||
Captured = control; |
|||
if (oldCapture != null) |
|||
{ |
|||
var commonParent = FindCommonParent(control, oldCapture); |
|||
foreach (var notifyTarget in oldCapture.GetSelfAndVisualAncestors().OfType<IInputElement>()) |
|||
{ |
|||
if (notifyTarget == commonParent) |
|||
break; |
|||
notifyTarget.RaiseEvent(new PointerCaptureLostEventArgs(notifyTarget, this)); |
|||
} |
|||
} |
|||
|
|||
if (Captured != null) |
|||
Captured.DetachedFromVisualTree += OnCaptureDetached; |
|||
} |
|||
|
|||
IInputElement GetNextCapture(IVisual parent) => |
|||
parent as IInputElement ?? parent.GetVisualAncestors().OfType<IInputElement>().FirstOrDefault(); |
|||
|
|||
private void OnCaptureDetached(object sender, VisualTreeAttachmentEventArgs e) |
|||
{ |
|||
Capture(GetNextCapture(e.Parent)); |
|||
} |
|||
|
|||
|
|||
public IInputElement Captured { get; private set; } |
|||
|
|||
public PointerType Type { get; } |
|||
public bool IsPrimary { get; } |
|||
public void Dispose() => Capture(null); |
|||
} |
|||
} |
|||
@ -0,0 +1,45 @@ |
|||
namespace Avalonia.Input |
|||
{ |
|||
public sealed class PointerPoint |
|||
{ |
|||
public PointerPoint(IPointer pointer, Point position, PointerPointProperties properties) |
|||
{ |
|||
Pointer = pointer; |
|||
Position = position; |
|||
Properties = properties; |
|||
} |
|||
public IPointer Pointer { get; } |
|||
public PointerPointProperties Properties { get; } |
|||
public Point Position { get; } |
|||
} |
|||
|
|||
public sealed class PointerPointProperties |
|||
{ |
|||
public bool IsLeftButtonPressed { get; set; } |
|||
public bool IsMiddleButtonPressed { get; set; } |
|||
public bool IsRightButtonPressed { get; set; } |
|||
|
|||
public PointerPointProperties() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public PointerPointProperties(InputModifiers modifiers) |
|||
{ |
|||
IsLeftButtonPressed = modifiers.HasFlag(InputModifiers.LeftMouseButton); |
|||
IsMiddleButtonPressed = modifiers.HasFlag(InputModifiers.MiddleMouseButton); |
|||
IsRightButtonPressed = modifiers.HasFlag(InputModifiers.RightMouseButton); |
|||
} |
|||
|
|||
public MouseButton GetObsoleteMouseButton() |
|||
{ |
|||
if (IsLeftButtonPressed) |
|||
return MouseButton.Left; |
|||
if (IsMiddleButtonPressed) |
|||
return MouseButton.Middle; |
|||
if (IsRightButtonPressed) |
|||
return MouseButton.Right; |
|||
return MouseButton.None; |
|||
} |
|||
} |
|||
} |
|||
@ -1,10 +1,22 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using Avalonia.Interactivity; |
|||
using Avalonia.VisualTree; |
|||
|
|||
namespace Avalonia.Input |
|||
{ |
|||
public class PointerWheelEventArgs : PointerEventArgs |
|||
{ |
|||
public Vector Delta { get; set; } |
|||
|
|||
public PointerWheelEventArgs(IInteractive source, IPointer pointer, IVisual rootVisual, |
|||
Point rootVisualPosition, ulong timestamp, |
|||
PointerPointProperties properties, InputModifiers modifiers, Vector delta) |
|||
: base(InputElement.PointerWheelChangedEvent, source, pointer, rootVisual, rootVisualPosition, |
|||
timestamp, properties, modifiers) |
|||
{ |
|||
Delta = delta; |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,15 @@ |
|||
namespace Avalonia.Input.Raw |
|||
{ |
|||
public class RawTouchEventArgs : RawPointerEventArgs |
|||
{ |
|||
public RawTouchEventArgs(IInputDevice device, ulong timestamp, IInputRoot root, |
|||
RawPointerEventType type, Point position, InputModifiers inputModifiers, |
|||
long touchPointId) |
|||
: base(device, timestamp, root, type, position, inputModifiers) |
|||
{ |
|||
TouchPointId = touchPointId; |
|||
} |
|||
|
|||
public long TouchPointId { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
using Avalonia.Interactivity; |
|||
|
|||
namespace Avalonia.Input |
|||
{ |
|||
public class ScrollGestureEventArgs : RoutedEventArgs |
|||
{ |
|||
public int Id { get; } |
|||
public Vector Delta { get; } |
|||
private static int _nextId = 1; |
|||
|
|||
public static int GetNextFreeId() => _nextId++; |
|||
|
|||
public ScrollGestureEventArgs(int id, Vector delta) : base(Gestures.ScrollGestureEvent) |
|||
{ |
|||
Id = id; |
|||
Delta = delta; |
|||
} |
|||
} |
|||
|
|||
public class ScrollGestureEndedEventArgs : RoutedEventArgs |
|||
{ |
|||
public int Id { get; } |
|||
|
|||
public ScrollGestureEndedEventArgs(int id) : base(Gestures.ScrollGestureEndedEvent) |
|||
{ |
|||
Id = id; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,74 @@ |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using Avalonia.Input.Raw; |
|||
using Avalonia.VisualTree; |
|||
|
|||
namespace Avalonia.Input |
|||
{ |
|||
/// <summary>
|
|||
/// Handles raw touch events
|
|||
/// <remarks>
|
|||
/// This class is supposed to be used on per-toplevel basis, don't use a shared one
|
|||
/// </remarks>
|
|||
/// </summary>
|
|||
public class TouchDevice : IInputDevice |
|||
{ |
|||
Dictionary<long, Pointer> _pointers = new Dictionary<long, Pointer>(); |
|||
|
|||
static InputModifiers GetModifiers(InputModifiers modifiers, bool left) |
|||
{ |
|||
var mask = (InputModifiers)0x7fffffff ^ InputModifiers.LeftMouseButton ^ InputModifiers.MiddleMouseButton ^ |
|||
InputModifiers.RightMouseButton; |
|||
modifiers &= mask; |
|||
if (left) |
|||
modifiers |= InputModifiers.LeftMouseButton; |
|||
return modifiers; |
|||
} |
|||
|
|||
public void ProcessRawEvent(RawInputEventArgs ev) |
|||
{ |
|||
var args = (RawTouchEventArgs)ev; |
|||
if (!_pointers.TryGetValue(args.TouchPointId, out var pointer)) |
|||
{ |
|||
if (args.Type == RawPointerEventType.TouchEnd) |
|||
return; |
|||
var hit = args.Root.InputHitTest(args.Position); |
|||
|
|||
_pointers[args.TouchPointId] = pointer = new Pointer(Pointer.GetNextFreeId(), |
|||
PointerType.Touch, _pointers.Count == 0); |
|||
pointer.Capture(hit); |
|||
} |
|||
|
|||
|
|||
var target = pointer.Captured ?? args.Root; |
|||
if (args.Type == RawPointerEventType.TouchBegin) |
|||
{ |
|||
target.RaiseEvent(new PointerPressedEventArgs(target, pointer, |
|||
args.Root, args.Position, ev.Timestamp, |
|||
new PointerPointProperties(GetModifiers(args.InputModifiers, pointer.IsPrimary)), |
|||
GetModifiers(args.InputModifiers, false))); |
|||
} |
|||
|
|||
if (args.Type == RawPointerEventType.TouchEnd) |
|||
{ |
|||
_pointers.Remove(args.TouchPointId); |
|||
using (pointer) |
|||
{ |
|||
target.RaiseEvent(new PointerReleasedEventArgs(target, pointer, |
|||
args.Root, args.Position, ev.Timestamp, |
|||
new PointerPointProperties(GetModifiers(args.InputModifiers, false)), |
|||
GetModifiers(args.InputModifiers, pointer.IsPrimary), |
|||
pointer.IsPrimary ? MouseButton.Left : MouseButton.None)); |
|||
} |
|||
} |
|||
|
|||
if (args.Type == RawPointerEventType.TouchUpdate) |
|||
{ |
|||
var modifiers = GetModifiers(args.InputModifiers, pointer.IsPrimary); |
|||
target.RaiseEvent(new PointerEventArgs(InputElement.PointerMovedEvent, target, pointer, args.Root, |
|||
args.Position, ev.Timestamp, new PointerPointProperties(modifiers), modifiers)); |
|||
} |
|||
} |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Avalonia.OpenGL |
|||
{ |
|||
public class AngleOptions |
|||
{ |
|||
public enum PlatformApi |
|||
{ |
|||
DirectX9, |
|||
DirectX11 |
|||
} |
|||
|
|||
public List<PlatformApi> AllowedPlatformApis = new List<PlatformApi> |
|||
{ |
|||
PlatformApi.DirectX9 |
|||
}; |
|||
} |
|||
} |
|||
@ -0,0 +1,55 @@ |
|||
using System; |
|||
using System.Linq; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Controls.Templates; |
|||
using Avalonia.Layout; |
|||
using Avalonia.Markup.Xaml; |
|||
using Avalonia.Markup.Xaml.Templates; |
|||
using ReactiveUI; |
|||
|
|||
namespace Avalonia.ReactiveUI |
|||
{ |
|||
/// <summary>
|
|||
/// AutoDataTemplateBindingHook is a binding hook that checks ItemsControls
|
|||
/// that don't have DataTemplates, and assigns a default DataTemplate that
|
|||
/// loads the View associated with each ViewModel.
|
|||
/// </summary>
|
|||
public class AutoDataTemplateBindingHook : IPropertyBindingHook |
|||
{ |
|||
private static FuncDataTemplate DefaultItemTemplate = new FuncDataTemplate<object>(x => |
|||
{ |
|||
var control = new ViewModelViewHost(); |
|||
var context = control.GetObservable(Control.DataContextProperty); |
|||
control.Bind(ViewModelViewHost.ViewModelProperty, context); |
|||
control.HorizontalContentAlignment = HorizontalAlignment.Stretch; |
|||
control.VerticalContentAlignment = VerticalAlignment.Stretch; |
|||
return control; |
|||
}, |
|||
true); |
|||
|
|||
/// <inheritdoc/>
|
|||
public bool ExecuteHook( |
|||
object source, object target, |
|||
Func<IObservedChange<object, object>[]> getCurrentViewModelProperties, |
|||
Func<IObservedChange<object, object>[]> getCurrentViewProperties, |
|||
BindingDirection direction) |
|||
{ |
|||
var viewProperties = getCurrentViewProperties(); |
|||
var lastViewProperty = viewProperties.LastOrDefault(); |
|||
var itemsControl = lastViewProperty?.Sender as ItemsControl; |
|||
if (itemsControl == null) |
|||
return true; |
|||
|
|||
var propertyName = viewProperties.Last().GetPropertyName(); |
|||
if (propertyName != "Items" && |
|||
propertyName != "ItemsSource") |
|||
return true; |
|||
|
|||
if (itemsControl.ItemTemplate != null) |
|||
return true; |
|||
|
|||
itemsControl.ItemTemplate = DefaultItemTemplate; |
|||
return true; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,75 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using Avalonia.Animation; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Styling; |
|||
|
|||
namespace Avalonia.ReactiveUI |
|||
{ |
|||
/// <summary>
|
|||
/// A ContentControl that animates the transition when its content is changed.
|
|||
/// </summary>
|
|||
public class TransitioningContentControl : ContentControl, IStyleable |
|||
{ |
|||
/// <summary>
|
|||
/// <see cref="AvaloniaProperty"/> for the <see cref="PageTransition"/> property.
|
|||
/// </summary>
|
|||
public static readonly AvaloniaProperty<IPageTransition> PageTransitionProperty = |
|||
AvaloniaProperty.Register<TransitioningContentControl, IPageTransition>(nameof(PageTransition), |
|||
new CrossFade(TimeSpan.FromSeconds(0.5))); |
|||
|
|||
/// <summary>
|
|||
/// <see cref="AvaloniaProperty"/> for the <see cref="DefaultContent"/> property.
|
|||
/// </summary>
|
|||
public static readonly AvaloniaProperty<object> DefaultContentProperty = |
|||
AvaloniaProperty.Register<TransitioningContentControl, object>(nameof(DefaultContent)); |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the animation played when content appears and disappears.
|
|||
/// </summary>
|
|||
public IPageTransition PageTransition |
|||
{ |
|||
get => GetValue(PageTransitionProperty); |
|||
set => SetValue(PageTransitionProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the content displayed whenever there is no page currently routed.
|
|||
/// </summary>
|
|||
public object DefaultContent |
|||
{ |
|||
get => GetValue(DefaultContentProperty); |
|||
set => SetValue(DefaultContentProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the content with animation.
|
|||
/// </summary>
|
|||
public new object Content |
|||
{ |
|||
get => base.Content; |
|||
set => UpdateContentWithTransition(value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// TransitioningContentControl uses the default ContentControl
|
|||
/// template from Avalonia default theme.
|
|||
/// </summary>
|
|||
Type IStyleable.StyleKey => typeof(ContentControl); |
|||
|
|||
/// <summary>
|
|||
/// Updates the content with transitions.
|
|||
/// </summary>
|
|||
/// <param name="content">New content to set.</param>
|
|||
private async void UpdateContentWithTransition(object content) |
|||
{ |
|||
if (PageTransition != null) |
|||
await PageTransition.Start(this, null, true); |
|||
base.Content = content; |
|||
if (PageTransition != null) |
|||
await PageTransition.Start(null, this, true); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,80 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Reactive.Disposables; |
|||
using ReactiveUI; |
|||
using Splat; |
|||
|
|||
namespace Avalonia.ReactiveUI |
|||
{ |
|||
/// <summary>
|
|||
/// This content control will automatically load the View associated with
|
|||
/// the ViewModel property and display it. This control is very useful
|
|||
/// inside a DataTemplate to display the View associated with a ViewModel.
|
|||
/// </summary>
|
|||
public class ViewModelViewHost : TransitioningContentControl, IViewFor, IEnableLogger |
|||
{ |
|||
/// <summary>
|
|||
/// <see cref="AvaloniaProperty"/> for the <see cref="ViewModel"/> property.
|
|||
/// </summary>
|
|||
public static readonly AvaloniaProperty<object> ViewModelProperty = |
|||
AvaloniaProperty.Register<ViewModelViewHost, object>(nameof(ViewModel)); |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ViewModelViewHost"/> class.
|
|||
/// </summary>
|
|||
public ViewModelViewHost() |
|||
{ |
|||
this.WhenActivated(disposables => |
|||
{ |
|||
this.WhenAnyValue(x => x.ViewModel) |
|||
.Subscribe(NavigateToViewModel) |
|||
.DisposeWith(disposables); |
|||
}); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the ViewModel to display.
|
|||
/// </summary>
|
|||
public object ViewModel |
|||
{ |
|||
get => GetValue(ViewModelProperty); |
|||
set => SetValue(ViewModelProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the view locator.
|
|||
/// </summary>
|
|||
public IViewLocator ViewLocator { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Invoked when ReactiveUI router navigates to a view model.
|
|||
/// </summary>
|
|||
/// <param name="viewModel">ViewModel to which the user navigates.</param>
|
|||
private void NavigateToViewModel(object viewModel) |
|||
{ |
|||
if (viewModel == null) |
|||
{ |
|||
this.Log().Info("ViewModel is null. Falling back to default content."); |
|||
Content = DefaultContent; |
|||
return; |
|||
} |
|||
|
|||
var viewLocator = ViewLocator ?? global::ReactiveUI.ViewLocator.Current; |
|||
var viewInstance = viewLocator.ResolveView(viewModel); |
|||
if (viewInstance == null) |
|||
{ |
|||
this.Log().Warn($"Couldn't find view for '{viewModel}'. Is it registered? Falling back to default content."); |
|||
Content = DefaultContent; |
|||
return; |
|||
} |
|||
|
|||
this.Log().Info($"Ready to show {viewInstance} with autowired {viewModel}."); |
|||
viewInstance.ViewModel = viewModel; |
|||
if (viewInstance is IStyledElement styled) |
|||
styled.DataContext = viewModel; |
|||
Content = viewInstance; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
using System; |
|||
using System.Diagnostics; |
|||
using System.Reactive.Disposables; |
|||
using Avalonia.Threading; |
|||
|
|||
namespace Avalonia.Rendering |
|||
{ |
|||
/// <summary>
|
|||
/// Render timer that ticks on UI thread. Useful for debugging or bootstrapping on new platforms
|
|||
/// </summary>
|
|||
|
|||
public class UiThreadRenderTimer : DefaultRenderTimer |
|||
{ |
|||
public UiThreadRenderTimer(int framesPerSecond) : base(framesPerSecond) |
|||
{ |
|||
} |
|||
|
|||
protected override IDisposable StartCore(Action<TimeSpan> tick) |
|||
{ |
|||
bool cancelled = false; |
|||
var st = Stopwatch.StartNew(); |
|||
DispatcherTimer.Run(() => |
|||
{ |
|||
if (cancelled) |
|||
return false; |
|||
tick(st.Elapsed); |
|||
return !cancelled; |
|||
}, TimeSpan.FromSeconds(1.0 / FramesPerSecond), DispatcherPriority.Render); |
|||
return Disposable.Create(() => cancelled = true); |
|||
} |
|||
} |
|||
} |
|||
@ -1 +1 @@ |
|||
Subproject commit 1e3ffc315401f0b2eb96a0e79b25c2fc19a80d78 |
|||
Subproject commit a73c5234831267b23160e01a9fbc83be633f69fc |
|||
@ -0,0 +1,111 @@ |
|||
using System.Reactive; |
|||
using Avalonia.Input; |
|||
using Avalonia.Interactivity; |
|||
using Avalonia.VisualTree; |
|||
|
|||
namespace Avalonia.Controls.UnitTests |
|||
{ |
|||
public class MouseTestHelper |
|||
{ |
|||
private Pointer _pointer = new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true); |
|||
private ulong _nextStamp = 1; |
|||
private ulong Timestamp() => _nextStamp++; |
|||
|
|||
private InputModifiers _pressedButtons; |
|||
public IInputElement Captured => _pointer.Captured; |
|||
|
|||
InputModifiers Convert(MouseButton mouseButton) |
|||
=> (mouseButton == MouseButton.Left ? InputModifiers.LeftMouseButton |
|||
: mouseButton == MouseButton.Middle ? InputModifiers.MiddleMouseButton |
|||
: mouseButton == MouseButton.Right ? InputModifiers.RightMouseButton : InputModifiers.None); |
|||
|
|||
int ButtonCount(PointerPointProperties props) |
|||
{ |
|||
var rv = 0; |
|||
if (props.IsLeftButtonPressed) |
|||
rv++; |
|||
if (props.IsMiddleButtonPressed) |
|||
rv++; |
|||
if (props.IsRightButtonPressed) |
|||
rv++; |
|||
return rv; |
|||
} |
|||
|
|||
private MouseButton _pressedButton; |
|||
|
|||
InputModifiers GetModifiers(InputModifiers modifiers) => modifiers | _pressedButtons; |
|||
|
|||
public void Down(IInteractive target, MouseButton mouseButton = MouseButton.Left, Point position = default, |
|||
InputModifiers modifiers = default, int clickCount = 1) |
|||
{ |
|||
Down(target, target, mouseButton, position, modifiers, clickCount); |
|||
} |
|||
|
|||
public void Down(IInteractive target, IInteractive source, MouseButton mouseButton = MouseButton.Left, |
|||
Point position = default, InputModifiers modifiers = default, int clickCount = 1) |
|||
{ |
|||
_pressedButtons |= Convert(mouseButton); |
|||
var props = new PointerPointProperties(_pressedButtons); |
|||
if (ButtonCount(props) > 1) |
|||
Move(target, source, position); |
|||
else |
|||
{ |
|||
_pressedButton = mouseButton; |
|||
_pointer.Capture((IInputElement)target); |
|||
target.RaiseEvent(new PointerPressedEventArgs(source, _pointer, (IVisual)source, position, Timestamp(), props, |
|||
GetModifiers(modifiers), clickCount)); |
|||
} |
|||
} |
|||
|
|||
public void Move(IInteractive target, in Point position, InputModifiers modifiers = default) => Move(target, target, position, modifiers); |
|||
public void Move(IInteractive target, IInteractive source, in Point position, InputModifiers modifiers = default) |
|||
{ |
|||
target.RaiseEvent(new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, (IVisual)target, position, |
|||
Timestamp(), new PointerPointProperties(_pressedButtons), GetModifiers(modifiers))); |
|||
} |
|||
|
|||
public void Up(IInteractive target, MouseButton mouseButton = MouseButton.Left, Point position = default, |
|||
InputModifiers modifiers = default) |
|||
=> Up(target, target, mouseButton, position, modifiers); |
|||
|
|||
public void Up(IInteractive target, IInteractive source, MouseButton mouseButton = MouseButton.Left, |
|||
Point position = default, InputModifiers modifiers = default) |
|||
{ |
|||
var conv = Convert(mouseButton); |
|||
_pressedButtons = (_pressedButtons | conv) ^ conv; |
|||
var props = new PointerPointProperties(_pressedButtons); |
|||
if (ButtonCount(props) == 0) |
|||
{ |
|||
_pointer.Capture(null); |
|||
target.RaiseEvent(new PointerReleasedEventArgs(source, _pointer, (IVisual)target, position, |
|||
Timestamp(), props, |
|||
GetModifiers(modifiers), _pressedButton)); |
|||
} |
|||
else |
|||
Move(target, source, position); |
|||
} |
|||
|
|||
public void Click(IInteractive target, MouseButton button = MouseButton.Left, Point position = default, |
|||
InputModifiers modifiers = default) |
|||
=> Click(target, target, button, position, modifiers); |
|||
public void Click(IInteractive target, IInteractive source, MouseButton button = MouseButton.Left, |
|||
Point position = default, InputModifiers modifiers = default) |
|||
{ |
|||
Down(target, source, button, position, modifiers); |
|||
Up(target, source, button, position, modifiers); |
|||
} |
|||
|
|||
public void Enter(IInteractive target) |
|||
{ |
|||
target.RaiseEvent(new PointerEventArgs(InputElement.PointerEnterEvent, target, _pointer, (IVisual)target, default, |
|||
Timestamp(), new PointerPointProperties(_pressedButtons), _pressedButtons)); |
|||
} |
|||
|
|||
public void Leave(IInteractive target) |
|||
{ |
|||
target.RaiseEvent(new PointerEventArgs(InputElement.PointerLeaveEvent, target, _pointer, (IVisual)target, default, |
|||
Timestamp(), new PointerPointProperties(_pressedButtons), _pressedButtons)); |
|||
} |
|||
|
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue