Browse Source

Merge branch 'master' into expander-events

pull/9555/head
robloo 3 years ago
committed by GitHub
parent
commit
dd60ea421b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      samples/ControlCatalog/ViewModels/TransitioningContentControlPageViewModel.cs
  2. 2
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  3. 2
      src/Avalonia.Base/Animation/PageSlide.cs
  4. 2
      src/Avalonia.Base/AttachedProperty.cs
  5. 2
      src/Avalonia.Base/AvaloniaObject.cs
  6. 100
      src/Avalonia.Base/AvaloniaObjectExtensions.cs
  7. 22
      src/Avalonia.Base/AvaloniaProperty.cs
  8. 6
      src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs
  9. 4
      src/Avalonia.Base/AvaloniaPropertyChangedEventArgs`1.cs
  10. 12
      src/Avalonia.Base/AvaloniaPropertyRegistry.cs
  11. 2
      src/Avalonia.Base/AvaloniaProperty`1.cs
  12. 4
      src/Avalonia.Base/ClassBindingManager.cs
  13. 2
      src/Avalonia.Base/Controls/Classes.cs
  14. 2
      src/Avalonia.Base/Controls/ISetInheritanceParent.cs
  15. 3
      src/Avalonia.Base/Controls/ResourceNodeExtensions.cs
  16. 4
      src/Avalonia.Base/Data/BindingOperations.cs
  17. 4
      src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs
  18. 2
      src/Avalonia.Base/Data/IBinding.cs
  19. 6
      src/Avalonia.Base/Data/IndexerBinding.cs
  20. 2
      src/Avalonia.Base/Data/InstancedBinding.cs
  21. 2
      src/Avalonia.Base/Diagnostics/IAvaloniaObjectDebug.cs
  22. 10
      src/Avalonia.Base/DirectProperty.cs
  23. 6
      src/Avalonia.Base/DirectPropertyBase.cs
  24. 79
      src/Avalonia.Base/IAvaloniaObject.cs
  25. 2
      src/Avalonia.Base/IDataContextProvider.cs
  26. 4
      src/Avalonia.Base/IDirectPropertyAccessor.cs
  27. 39
      src/Avalonia.Base/IStyledElement.cs
  28. 4
      src/Avalonia.Base/Input/AccessKeyHandler.cs
  29. 5
      src/Avalonia.Base/Input/DragDropDevice.cs
  30. 2
      src/Avalonia.Base/Input/DragEventArgs.cs
  31. 17
      src/Avalonia.Base/Input/FocusManager.cs
  32. 4
      src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs
  33. 4
      src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs
  34. 33
      src/Avalonia.Base/Input/Gestures.cs
  35. 39
      src/Avalonia.Base/Input/IInputElement.cs
  36. 2
      src/Avalonia.Base/Input/IMainMenu.cs
  37. 17
      src/Avalonia.Base/Input/InputExtensions.cs
  38. 43
      src/Avalonia.Base/Input/KeyboardDevice.cs
  39. 2
      src/Avalonia.Base/Input/KeyboardNavigation.cs
  40. 6
      src/Avalonia.Base/Input/KeyboardNavigationHandler.cs
  41. 14
      src/Avalonia.Base/Input/MouseDevice.cs
  42. 12
      src/Avalonia.Base/Input/Navigation/FocusExtensions.cs
  43. 18
      src/Avalonia.Base/Input/Navigation/TabNavigation.cs
  44. 6
      src/Avalonia.Base/Input/PenDevice.cs
  45. 20
      src/Avalonia.Base/Input/Pointer.cs
  46. 4
      src/Avalonia.Base/Input/PointerDeltaEventArgs.cs
  47. 30
      src/Avalonia.Base/Input/PointerEventArgs.cs
  48. 48
      src/Avalonia.Base/Input/PointerOverPreProcessor.cs
  49. 2
      src/Avalonia.Base/Input/PointerWheelEventArgs.cs
  50. 2
      src/Avalonia.Base/Input/TappedEventArgs.cs
  51. 2
      src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs
  52. 9
      src/Avalonia.Base/Input/TextInput/InputMethodManager.cs
  53. 6
      src/Avalonia.Base/Input/TextInput/TransformTrackingHelper.cs
  54. 6
      src/Avalonia.Base/Input/TouchDevice.cs
  55. 12
      src/Avalonia.Base/Interactivity/EventRoute.cs
  56. 53
      src/Avalonia.Base/Interactivity/IInteractive.cs
  57. 40
      src/Avalonia.Base/Interactivity/Interactive.cs
  58. 8
      src/Avalonia.Base/Interactivity/InteractiveExtensions.cs
  59. 2
      src/Avalonia.Base/Interactivity/RoutedEvent.cs
  60. 4
      src/Avalonia.Base/Interactivity/RoutedEventArgs.cs
  61. 2
      src/Avalonia.Base/Layout/AttachedLayout.cs
  62. 14
      src/Avalonia.Base/Layout/ElementManager.cs
  63. 10
      src/Avalonia.Base/Layout/FlowLayoutAlgorithm.cs
  64. 6
      src/Avalonia.Base/Layout/IFlowLayoutAlgorithmDelegates.cs
  65. 8
      src/Avalonia.Base/Layout/ILayoutManager.cs
  66. 2
      src/Avalonia.Base/Layout/ILayoutRoot.cs
  67. 122
      src/Avalonia.Base/Layout/ILayoutable.cs
  68. 4
      src/Avalonia.Base/Layout/LayoutContextAdapter.cs
  69. 43
      src/Avalonia.Base/Layout/LayoutHelper.cs
  70. 38
      src/Avalonia.Base/Layout/LayoutManager.cs
  71. 35
      src/Avalonia.Base/Layout/Layoutable.cs
  72. 4
      src/Avalonia.Base/Layout/NonVirtualizingLayoutContext.cs
  73. 8
      src/Avalonia.Base/Layout/NonVirtualizingStackLayout.cs
  74. 12
      src/Avalonia.Base/Layout/StackLayout.cs
  75. 6
      src/Avalonia.Base/Layout/UniformGridLayout.cs
  76. 4
      src/Avalonia.Base/Layout/UniformGridLayoutState.cs
  77. 8
      src/Avalonia.Base/Layout/VirtualLayoutContextAdapter.cs
  78. 12
      src/Avalonia.Base/Layout/VirtualizingLayoutContext.cs
  79. 2
      src/Avalonia.Base/Layout/WrapLayout/WrapItem.cs
  80. 4
      src/Avalonia.Base/Media/IVisualBrush.cs
  81. 2
      src/Avalonia.Base/Media/Imaging/RenderTargetBitmap.cs
  82. 4
      src/Avalonia.Base/Media/Immutable/ImmutableVisualBrush.cs
  83. 10
      src/Avalonia.Base/Media/VisualBrush.cs
  84. 2
      src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs
  85. 12
      src/Avalonia.Base/PropertyStore/UntypedValueUtils.cs
  86. 6
      src/Avalonia.Base/Reactive/AvaloniaPropertyBindingObservable.cs
  87. 6
      src/Avalonia.Base/Reactive/AvaloniaPropertyChangedObservable.cs
  88. 36
      src/Avalonia.Base/Reactive/AvaloniaPropertyObservable.cs
  89. 14
      src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
  90. 22
      src/Avalonia.Base/Rendering/DeferredRenderer.cs
  91. 14
      src/Avalonia.Base/Rendering/DirtyVisuals.cs
  92. 4
      src/Avalonia.Base/Rendering/ICustomSimpleHitTest.cs
  93. 2
      src/Avalonia.Base/Rendering/IRenderRoot.cs
  94. 8
      src/Avalonia.Base/Rendering/IRenderer.cs
  95. 38
      src/Avalonia.Base/Rendering/ImmediateRenderer.cs
  96. 4
      src/Avalonia.Base/Rendering/RenderLayer.cs
  97. 7
      src/Avalonia.Base/Rendering/RenderLayers.cs
  98. 4
      src/Avalonia.Base/Rendering/SceneGraph/ISceneBuilder.cs
  99. 5
      src/Avalonia.Base/Rendering/SceneGraph/IVisualNode.cs
  100. 40
      src/Avalonia.Base/Rendering/SceneGraph/Scene.cs

6
samples/ControlCatalog/ViewModels/TransitioningContentControlPageViewModel.cs

@ -293,10 +293,10 @@ namespace ControlCatalog.ViewModels
/// <remarks> /// <remarks>
/// Any one of the parameters may be null, but not both. /// Any one of the parameters may be null, but not both.
/// </remarks> /// </remarks>
private static IVisual GetVisualParent(IVisual? from, IVisual? to) private static Visual GetVisualParent(Visual? from, Visual? to)
{ {
var p1 = (from ?? to)!.VisualParent; var p1 = (from ?? to)!.GetVisualParent();
var p2 = (to ?? from)!.VisualParent; var p2 = (to ?? from)!.GetVisualParent();
if (p1 != null && p2 != null && p1 != p2) if (p1 != null && p2 != null && p1 != p2)
{ {

2
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@ -109,7 +109,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
? new CompositingRenderer(root, AndroidPlatform.Compositor) ? new CompositingRenderer(root, AndroidPlatform.Compositor)
: AndroidPlatform.Options.UseDeferredRendering : AndroidPlatform.Options.UseDeferredRendering
? new DeferredRenderer(root, AvaloniaLocator.Current.GetRequiredService<IRenderLoop>()) { RenderOnlyOnRenderThread = true } ? new DeferredRenderer(root, AvaloniaLocator.Current.GetRequiredService<IRenderLoop>()) { RenderOnlyOnRenderThread = true }
: new ImmediateRenderer(root); : new ImmediateRenderer((Visual)root);
public virtual void Hide() public virtual void Hide()
{ {

2
src/Avalonia.Base/Animation/PageSlide.cs

@ -157,7 +157,7 @@ namespace Avalonia.Animation
/// <remarks> /// <remarks>
/// Any one of the parameters may be null, but not both. /// Any one of the parameters may be null, but not both.
/// </remarks> /// </remarks>
protected static IVisual GetVisualParent(IVisual? from, IVisual? to) protected static Visual GetVisualParent(Visual? from, Visual? to)
{ {
var p1 = (from ?? to)!.VisualParent; var p1 = (from ?? to)!.VisualParent;
var p2 = (to ?? from)!.VisualParent; var p2 = (to ?? from)!.VisualParent;

2
src/Avalonia.Base/AttachedProperty.cs

@ -34,7 +34,7 @@ namespace Avalonia
/// </summary> /// </summary>
/// <typeparam name="TOwner">The owner type.</typeparam> /// <typeparam name="TOwner">The owner type.</typeparam>
/// <returns>The property.</returns> /// <returns>The property.</returns>
public new AttachedProperty<TValue> AddOwner<TOwner>() where TOwner : IAvaloniaObject public new AttachedProperty<TValue> AddOwner<TOwner>() where TOwner : AvaloniaObject
{ {
AvaloniaPropertyRegistry.Instance.Register(typeof(TOwner), this); AvaloniaPropertyRegistry.Instance.Register(typeof(TOwner), this);
return this; return this;

2
src/Avalonia.Base/AvaloniaObject.cs

@ -16,7 +16,7 @@ namespace Avalonia
/// <remarks> /// <remarks>
/// This class is analogous to DependencyObject in WPF. /// This class is analogous to DependencyObject in WPF.
/// </remarks> /// </remarks>
public class AvaloniaObject : IAvaloniaObject, IAvaloniaObjectDebug, INotifyPropertyChanged public class AvaloniaObject : IAvaloniaObjectDebug, INotifyPropertyChanged
{ {
private readonly ValueStore _values; private readonly ValueStore _values;
private AvaloniaObject? _inheritanceParent; private AvaloniaObject? _inheritanceParent;

100
src/Avalonia.Base/AvaloniaObjectExtensions.cs

@ -36,7 +36,7 @@ namespace Avalonia
/// <remarks> /// <remarks>
/// The subscription to <paramref name="o"/> is created using a weak reference. /// The subscription to <paramref name="o"/> is created using a weak reference.
/// </remarks> /// </remarks>
public static IObservable<object?> GetObservable(this IAvaloniaObject o, AvaloniaProperty property) public static IObservable<object?> GetObservable(this AvaloniaObject o, AvaloniaProperty property)
{ {
return new AvaloniaPropertyObservable<object?>( return new AvaloniaPropertyObservable<object?>(
o ?? throw new ArgumentNullException(nameof(o)), o ?? throw new ArgumentNullException(nameof(o)),
@ -56,7 +56,7 @@ namespace Avalonia
/// <remarks> /// <remarks>
/// The subscription to <paramref name="o"/> is created using a weak reference. /// The subscription to <paramref name="o"/> is created using a weak reference.
/// </remarks> /// </remarks>
public static IObservable<T> GetObservable<T>(this IAvaloniaObject o, AvaloniaProperty<T> property) public static IObservable<T> GetObservable<T>(this AvaloniaObject o, AvaloniaProperty<T> property)
{ {
return new AvaloniaPropertyObservable<T>( return new AvaloniaPropertyObservable<T>(
o ?? throw new ArgumentNullException(nameof(o)), o ?? throw new ArgumentNullException(nameof(o)),
@ -76,7 +76,7 @@ namespace Avalonia
/// The subscription to <paramref name="o"/> is created using a weak reference. /// The subscription to <paramref name="o"/> is created using a weak reference.
/// </remarks> /// </remarks>
public static IObservable<BindingValue<object?>> GetBindingObservable( public static IObservable<BindingValue<object?>> GetBindingObservable(
this IAvaloniaObject o, this AvaloniaObject o,
AvaloniaProperty property) AvaloniaProperty property)
{ {
return new AvaloniaPropertyBindingObservable<object?>( return new AvaloniaPropertyBindingObservable<object?>(
@ -98,7 +98,7 @@ namespace Avalonia
/// The subscription to <paramref name="o"/> is created using a weak reference. /// The subscription to <paramref name="o"/> is created using a weak reference.
/// </remarks> /// </remarks>
public static IObservable<BindingValue<T>> GetBindingObservable<T>( public static IObservable<BindingValue<T>> GetBindingObservable<T>(
this IAvaloniaObject o, this AvaloniaObject o,
AvaloniaProperty<T> property) AvaloniaProperty<T> property)
{ {
return new AvaloniaPropertyBindingObservable<T>( return new AvaloniaPropertyBindingObservable<T>(
@ -115,11 +115,11 @@ namespace Avalonia
/// <param name="property">The property.</param> /// <param name="property">The property.</param>
/// <returns> /// <returns>
/// An observable which when subscribed pushes the property changed event args /// An observable which when subscribed pushes the property changed event args
/// each time a <see cref="IAvaloniaObject.PropertyChanged"/> event is raised /// each time a <see cref="AvaloniaObject.PropertyChanged"/> event is raised
/// for the specified property. /// for the specified property.
/// </returns> /// </returns>
public static IObservable<AvaloniaPropertyChangedEventArgs> GetPropertyChangedObservable( public static IObservable<AvaloniaPropertyChangedEventArgs> GetPropertyChangedObservable(
this IAvaloniaObject o, this AvaloniaObject o,
AvaloniaProperty property) AvaloniaProperty property)
{ {
return new AvaloniaPropertyChangedObservable( return new AvaloniaPropertyChangedObservable(
@ -140,7 +140,7 @@ namespace Avalonia
/// property. /// property.
/// </returns> /// </returns>
public static ISubject<object?> GetSubject( public static ISubject<object?> GetSubject(
this IAvaloniaObject o, this AvaloniaObject o,
AvaloniaProperty property, AvaloniaProperty property,
BindingPriority priority = BindingPriority.LocalValue) BindingPriority priority = BindingPriority.LocalValue)
{ {
@ -163,7 +163,7 @@ namespace Avalonia
/// property. /// property.
/// </returns> /// </returns>
public static ISubject<T> GetSubject<T>( public static ISubject<T> GetSubject<T>(
this IAvaloniaObject o, this AvaloniaObject o,
AvaloniaProperty<T> property, AvaloniaProperty<T> property,
BindingPriority priority = BindingPriority.LocalValue) BindingPriority priority = BindingPriority.LocalValue)
{ {
@ -185,7 +185,7 @@ namespace Avalonia
/// property. /// property.
/// </returns> /// </returns>
public static ISubject<BindingValue<object?>> GetBindingSubject( public static ISubject<BindingValue<object?>> GetBindingSubject(
this IAvaloniaObject o, this AvaloniaObject o,
AvaloniaProperty property, AvaloniaProperty property,
BindingPriority priority = BindingPriority.LocalValue) BindingPriority priority = BindingPriority.LocalValue)
{ {
@ -214,7 +214,7 @@ namespace Avalonia
/// property. /// property.
/// </returns> /// </returns>
public static ISubject<BindingValue<T>> GetBindingSubject<T>( public static ISubject<BindingValue<T>> GetBindingSubject<T>(
this IAvaloniaObject o, this AvaloniaObject o,
AvaloniaProperty<T> property, AvaloniaProperty<T> property,
BindingPriority priority = BindingPriority.LocalValue) BindingPriority priority = BindingPriority.LocalValue)
{ {
@ -241,7 +241,7 @@ namespace Avalonia
/// A disposable which can be used to terminate the binding. /// A disposable which can be used to terminate the binding.
/// </returns> /// </returns>
public static IDisposable Bind<T>( public static IDisposable Bind<T>(
this IAvaloniaObject target, this AvaloniaObject target,
AvaloniaProperty<T> property, AvaloniaProperty<T> property,
IObservable<BindingValue<T>> source, IObservable<BindingValue<T>> source,
BindingPriority priority = BindingPriority.LocalValue) BindingPriority priority = BindingPriority.LocalValue)
@ -250,17 +250,12 @@ namespace Avalonia
property = property ?? throw new ArgumentNullException(nameof(property)); property = property ?? throw new ArgumentNullException(nameof(property));
source = source ?? throw new ArgumentNullException(nameof(source)); source = source ?? throw new ArgumentNullException(nameof(source));
if (target is AvaloniaObject ao) return property switch
{ {
return property switch StyledPropertyBase<T> styled => target.Bind(styled, source, priority),
{ DirectPropertyBase<T> direct => target.Bind(direct, source),
StyledPropertyBase<T> styled => ao.Bind(styled, source, priority), _ => throw new NotSupportedException("Unsupported AvaloniaProperty type."),
DirectPropertyBase<T> direct => ao.Bind(direct, source), };
_ => throw new NotSupportedException("Unsupported AvaloniaProperty type."),
};
}
throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported.");
} }
/// <summary> /// <summary>
@ -274,22 +269,17 @@ namespace Avalonia
/// A disposable which can be used to terminate the binding. /// A disposable which can be used to terminate the binding.
/// </returns> /// </returns>
public static IDisposable Bind<T>( public static IDisposable Bind<T>(
this IAvaloniaObject target, this AvaloniaObject target,
AvaloniaProperty<T> property, AvaloniaProperty<T> property,
IObservable<T> source, IObservable<T> source,
BindingPriority priority = BindingPriority.LocalValue) BindingPriority priority = BindingPriority.LocalValue)
{ {
if (target is AvaloniaObject ao) return property switch
{ {
return property switch StyledPropertyBase<T> styled => target.Bind(styled, source, priority),
{ DirectPropertyBase<T> direct => target.Bind(direct, source),
StyledPropertyBase<T> styled => ao.Bind(styled, source, priority), _ => throw new NotSupportedException("Unsupported AvaloniaProperty type."),
DirectPropertyBase<T> direct => ao.Bind(direct, source), };
_ => throw new NotSupportedException("Unsupported AvaloniaProperty type."),
};
}
throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported.");
} }
/// <summary> /// <summary>
@ -306,7 +296,7 @@ namespace Avalonia
/// </param> /// </param>
/// <returns>An <see cref="IDisposable"/> which can be used to cancel the binding.</returns> /// <returns>An <see cref="IDisposable"/> which can be used to cancel the binding.</returns>
public static IDisposable Bind( public static IDisposable Bind(
this IAvaloniaObject target, this AvaloniaObject target,
AvaloniaProperty property, AvaloniaProperty property,
IBinding binding, IBinding binding,
object? anchor = null) object? anchor = null)
@ -340,23 +330,17 @@ namespace Avalonia
/// <param name="target">The object.</param> /// <param name="target">The object.</param>
/// <param name="property">The property.</param> /// <param name="property">The property.</param>
/// <returns>The value.</returns> /// <returns>The value.</returns>
public static T GetValue<T>(this IAvaloniaObject target, AvaloniaProperty<T> property) public static T GetValue<T>(this AvaloniaObject target, AvaloniaProperty<T> property)
{ {
target = target ?? throw new ArgumentNullException(nameof(target)); target = target ?? throw new ArgumentNullException(nameof(target));
property = property ?? throw new ArgumentNullException(nameof(property)); property = property ?? throw new ArgumentNullException(nameof(property));
if (target is AvaloniaObject ao) return property switch
{ {
return property switch StyledPropertyBase<T> styled => target.GetValue(styled),
{ DirectPropertyBase<T> direct => target.GetValue(direct),
StyledPropertyBase<T> styled => ao.GetValue(styled), _ => throw new NotSupportedException("Unsupported AvaloniaProperty type.")
DirectPropertyBase<T> direct => ao.GetValue(direct), };
_ => throw new NotSupportedException("Unsupported AvaloniaProperty type.")
};
}
throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported.");
} }
/// <summary> /// <summary>
@ -372,15 +356,13 @@ namespace Avalonia
/// For direct properties returns the current value of the property. /// For direct properties returns the current value of the property.
/// </remarks> /// </remarks>
public static object? GetBaseValue( public static object? GetBaseValue(
this IAvaloniaObject target, this AvaloniaObject target,
AvaloniaProperty property) AvaloniaProperty property)
{ {
target = target ?? throw new ArgumentNullException(nameof(target)); target = target ?? throw new ArgumentNullException(nameof(target));
property = property ?? throw new ArgumentNullException(nameof(property)); property = property ?? throw new ArgumentNullException(nameof(property));
if (target is AvaloniaObject ao) return property.RouteGetBaseValue(target);
return property.RouteGetBaseValue(ao);
throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported.");
} }
/// <summary> /// <summary>
@ -396,24 +378,18 @@ namespace Avalonia
/// For direct properties returns the current value of the property. /// For direct properties returns the current value of the property.
/// </remarks> /// </remarks>
public static Optional<T> GetBaseValue<T>( public static Optional<T> GetBaseValue<T>(
this IAvaloniaObject target, this AvaloniaObject target,
AvaloniaProperty<T> property) AvaloniaProperty<T> property)
{ {
target = target ?? throw new ArgumentNullException(nameof(target)); target = target ?? throw new ArgumentNullException(nameof(target));
property = property ?? throw new ArgumentNullException(nameof(property)); property = property ?? throw new ArgumentNullException(nameof(property));
if (target is AvaloniaObject ao) return property switch
{ {
return property switch StyledPropertyBase<T> styled => target.GetBaseValue(styled),
{ DirectPropertyBase<T> direct => target.GetValue(direct),
StyledPropertyBase<T> styled => ao.GetBaseValue(styled), _ => throw new NotSupportedException("Unsupported AvaloniaProperty type.")
DirectPropertyBase<T> direct => ao.GetValue(direct), };
_ => throw new NotSupportedException("Unsupported AvaloniaProperty type.")
};
}
throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported.");
} }
/// <summary> /// <summary>
@ -474,7 +450,7 @@ namespace Avalonia
} }
public InstancedBinding? Initiate( public InstancedBinding? Initiate(
IAvaloniaObject target, AvaloniaObject target,
AvaloniaProperty? targetProperty, AvaloniaProperty? targetProperty,
object? anchor = null, object? anchor = null,
bool enableDataValidation = false) bool enableDataValidation = false)

22
src/Avalonia.Base/AvaloniaProperty.cs

@ -38,7 +38,7 @@ namespace Avalonia
Type valueType, Type valueType,
Type ownerType, Type ownerType,
AvaloniaPropertyMetadata metadata, AvaloniaPropertyMetadata metadata,
Action<IAvaloniaObject, bool>? notifying = null) Action<AvaloniaObject, bool>? notifying = null)
{ {
_ = name ?? throw new ArgumentNullException(nameof(name)); _ = name ?? throw new ArgumentNullException(nameof(name));
@ -145,7 +145,7 @@ namespace Avalonia
/// will be true before the property change notifications are sent and false afterwards. This /// will be true before the property change notifications are sent and false afterwards. This
/// callback is intended to support Control.IsDataContextChanging. /// callback is intended to support Control.IsDataContextChanging.
/// </remarks> /// </remarks>
public Action<IAvaloniaObject, bool>? Notifying { get; } public Action<AvaloniaObject, bool>? Notifying { get; }
/// <summary> /// <summary>
/// Gets the integer ID that represents this property. /// Gets the integer ID that represents this property.
@ -238,9 +238,9 @@ namespace Avalonia
bool inherits = false, bool inherits = false,
BindingMode defaultBindingMode = BindingMode.OneWay, BindingMode defaultBindingMode = BindingMode.OneWay,
Func<TValue, bool>? validate = null, Func<TValue, bool>? validate = null,
Func<IAvaloniaObject, TValue, TValue>? coerce = null, Func<AvaloniaObject, TValue, TValue>? coerce = null,
Action<IAvaloniaObject, bool>? notifying = null) Action<AvaloniaObject, bool>? notifying = null)
where TOwner : IAvaloniaObject where TOwner : AvaloniaObject
{ {
_ = name ?? throw new ArgumentNullException(nameof(name)); _ = name ?? throw new ArgumentNullException(nameof(name));
@ -279,8 +279,8 @@ namespace Avalonia
bool inherits = false, bool inherits = false,
BindingMode defaultBindingMode = BindingMode.OneWay, BindingMode defaultBindingMode = BindingMode.OneWay,
Func<TValue, bool>? validate = null, Func<TValue, bool>? validate = null,
Func<IAvaloniaObject, TValue, TValue>? coerce = null) Func<AvaloniaObject, TValue, TValue>? coerce = null)
where THost : IAvaloniaObject where THost : AvaloniaObject
{ {
_ = name ?? throw new ArgumentNullException(nameof(name)); _ = name ?? throw new ArgumentNullException(nameof(name));
@ -316,8 +316,8 @@ namespace Avalonia
bool inherits = false, bool inherits = false,
BindingMode defaultBindingMode = BindingMode.OneWay, BindingMode defaultBindingMode = BindingMode.OneWay,
Func<TValue, bool>? validate = null, Func<TValue, bool>? validate = null,
Func<IAvaloniaObject, TValue, TValue>? coerce = null) Func<AvaloniaObject, TValue, TValue>? coerce = null)
where THost : IAvaloniaObject where THost : AvaloniaObject
{ {
_ = name ?? throw new ArgumentNullException(nameof(name)); _ = name ?? throw new ArgumentNullException(nameof(name));
@ -354,7 +354,7 @@ namespace Avalonia
TValue unsetValue = default!, TValue unsetValue = default!,
BindingMode defaultBindingMode = BindingMode.OneWay, BindingMode defaultBindingMode = BindingMode.OneWay,
bool enableDataValidation = false) bool enableDataValidation = false)
where TOwner : IAvaloniaObject where TOwner : AvaloniaObject
{ {
_ = name ?? throw new ArgumentNullException(nameof(name)); _ = name ?? throw new ArgumentNullException(nameof(name));
_ = getter ?? throw new ArgumentNullException(nameof(getter)); _ = getter ?? throw new ArgumentNullException(nameof(getter));
@ -415,7 +415,7 @@ namespace Avalonia
/// <returns> /// <returns>
/// The property metadata. /// The property metadata.
/// </returns> /// </returns>
public AvaloniaPropertyMetadata GetMetadata<T>() where T : IAvaloniaObject public AvaloniaPropertyMetadata GetMetadata<T>() where T : AvaloniaObject
{ {
return GetMetadata(typeof(T)); return GetMetadata(typeof(T));
} }

6
src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs

@ -9,7 +9,7 @@ namespace Avalonia
public abstract class AvaloniaPropertyChangedEventArgs : EventArgs public abstract class AvaloniaPropertyChangedEventArgs : EventArgs
{ {
public AvaloniaPropertyChangedEventArgs( public AvaloniaPropertyChangedEventArgs(
IAvaloniaObject sender, AvaloniaObject sender,
BindingPriority priority) BindingPriority priority)
{ {
Sender = sender; Sender = sender;
@ -18,7 +18,7 @@ namespace Avalonia
} }
internal AvaloniaPropertyChangedEventArgs( internal AvaloniaPropertyChangedEventArgs(
IAvaloniaObject sender, AvaloniaObject sender,
BindingPriority priority, BindingPriority priority,
bool isEffectiveValueChange) bool isEffectiveValueChange)
{ {
@ -31,7 +31,7 @@ namespace Avalonia
/// Gets the <see cref="AvaloniaObject"/> that the property changed on. /// Gets the <see cref="AvaloniaObject"/> that the property changed on.
/// </summary> /// </summary>
/// <value>The sender object.</value> /// <value>The sender object.</value>
public IAvaloniaObject Sender { get; } public AvaloniaObject Sender { get; }
/// <summary> /// <summary>
/// Gets the property that changed. /// Gets the property that changed.

4
src/Avalonia.Base/AvaloniaPropertyChangedEventArgs`1.cs

@ -16,7 +16,7 @@ namespace Avalonia
/// <param name="newValue">The new value of the property.</param> /// <param name="newValue">The new value of the property.</param>
/// <param name="priority">The priority of the binding that produced the value.</param> /// <param name="priority">The priority of the binding that produced the value.</param>
public AvaloniaPropertyChangedEventArgs( public AvaloniaPropertyChangedEventArgs(
IAvaloniaObject sender, AvaloniaObject sender,
AvaloniaProperty<T> property, AvaloniaProperty<T> property,
Optional<T> oldValue, Optional<T> oldValue,
BindingValue<T> newValue, BindingValue<T> newValue,
@ -26,7 +26,7 @@ namespace Avalonia
} }
internal AvaloniaPropertyChangedEventArgs( internal AvaloniaPropertyChangedEventArgs(
IAvaloniaObject sender, AvaloniaObject sender,
AvaloniaProperty<T> property, AvaloniaProperty<T> property,
Optional<T> oldValue, Optional<T> oldValue,
BindingValue<T> newValue, BindingValue<T> newValue,

12
src/Avalonia.Base/AvaloniaPropertyRegistry.cs

@ -189,7 +189,7 @@ namespace Avalonia
/// </summary> /// </summary>
/// <param name="o">The object.</param> /// <param name="o">The object.</param>
/// <returns>A collection of <see cref="AvaloniaProperty"/> definitions.</returns> /// <returns>A collection of <see cref="AvaloniaProperty"/> definitions.</returns>
public IReadOnlyList<AvaloniaProperty> GetRegistered(IAvaloniaObject o) public IReadOnlyList<AvaloniaProperty> GetRegistered(AvaloniaObject o)
{ {
_ = o ?? throw new ArgumentNullException(nameof(o)); _ = o ?? throw new ArgumentNullException(nameof(o));
@ -205,7 +205,7 @@ namespace Avalonia
/// The registered. /// The registered.
/// </returns> /// </returns>
public DirectPropertyBase<T> GetRegisteredDirect<T>( public DirectPropertyBase<T> GetRegisteredDirect<T>(
IAvaloniaObject o, AvaloniaObject o,
DirectPropertyBase<T> property) DirectPropertyBase<T> property)
{ {
return FindRegisteredDirect(o, property) ?? return FindRegisteredDirect(o, property) ??
@ -260,7 +260,7 @@ namespace Avalonia
/// <exception cref="InvalidOperationException"> /// <exception cref="InvalidOperationException">
/// The property name contains a '.'. /// The property name contains a '.'.
/// </exception> /// </exception>
public AvaloniaProperty? FindRegistered(IAvaloniaObject o, string name) public AvaloniaProperty? FindRegistered(AvaloniaObject o, string name)
{ {
_ = o ?? throw new ArgumentNullException(nameof(o)); _ = o ?? throw new ArgumentNullException(nameof(o));
_ = name ?? throw new ArgumentNullException(nameof(name)); _ = name ?? throw new ArgumentNullException(nameof(name));
@ -277,7 +277,7 @@ namespace Avalonia
/// The registered property or null if no matching property found. /// The registered property or null if no matching property found.
/// </returns> /// </returns>
public DirectPropertyBase<T>? FindRegisteredDirect<T>( public DirectPropertyBase<T>? FindRegisteredDirect<T>(
IAvaloniaObject o, AvaloniaObject o,
DirectPropertyBase<T> property) DirectPropertyBase<T> property)
{ {
if (property.Owner == o.GetType()) if (property.Owner == o.GetType())
@ -362,7 +362,7 @@ namespace Avalonia
/// <param name="property">The property.</param> /// <param name="property">The property.</param>
/// <remarks> /// <remarks>
/// You won't usually want to call this method directly, instead use the /// You won't usually want to call this method directly, instead use the
/// <see cref="AvaloniaProperty.Register{TOwner, TValue}(string, TValue, bool, Data.BindingMode, Func{TValue, bool}, Func{IAvaloniaObject, TValue, TValue}, Action{IAvaloniaObject, bool})"/> /// <see cref="AvaloniaProperty.Register{TOwner, TValue}(string, TValue, bool, Data.BindingMode, Func{TValue, bool}, Func{AvaloniaObject, TValue, TValue}, Action{AvaloniaObject, bool})"/>
/// method. /// method.
/// </remarks> /// </remarks>
public void Register(Type type, AvaloniaProperty property) public void Register(Type type, AvaloniaProperty property)
@ -413,7 +413,7 @@ namespace Avalonia
/// <param name="property">The property.</param> /// <param name="property">The property.</param>
/// <remarks> /// <remarks>
/// You won't usually want to call this method directly, instead use the /// You won't usually want to call this method directly, instead use the
/// <see cref="AvaloniaProperty.RegisterAttached{THost, TValue}(string, Type, TValue, bool, Data.BindingMode, Func{TValue, bool}, Func{IAvaloniaObject, TValue, TValue})"/> /// <see cref="AvaloniaProperty.RegisterAttached{THost, TValue}(string, Type, TValue, bool, Data.BindingMode, Func{TValue, bool}, Func{AvaloniaObject, TValue, TValue})"/>
/// method. /// method.
/// </remarks> /// </remarks>
public void RegisterAttached(Type type, AvaloniaProperty property) public void RegisterAttached(Type type, AvaloniaProperty property)

2
src/Avalonia.Base/AvaloniaProperty`1.cs

@ -24,7 +24,7 @@ namespace Avalonia
string name, string name,
Type ownerType, Type ownerType,
AvaloniaPropertyMetadata metadata, AvaloniaPropertyMetadata metadata,
Action<IAvaloniaObject, bool>? notifying = null) Action<AvaloniaObject, bool>? notifying = null)
: base(name, typeof(TValue), ownerType, metadata, notifying) : base(name, typeof(TValue), ownerType, metadata, notifying)
{ {
_changed = new Subject<AvaloniaPropertyChangedEventArgs<TValue>>(); _changed = new Subject<AvaloniaPropertyChangedEventArgs<TValue>>();

4
src/Avalonia.Base/ClassBindingManager.cs

@ -9,7 +9,7 @@ namespace Avalonia
private static readonly Dictionary<string, AvaloniaProperty> s_RegisteredProperties = private static readonly Dictionary<string, AvaloniaProperty> s_RegisteredProperties =
new Dictionary<string, AvaloniaProperty>(); new Dictionary<string, AvaloniaProperty>();
public static IDisposable Bind(IStyledElement target, string className, IBinding source, object anchor) public static IDisposable Bind(StyledElement target, string className, IBinding source, object anchor)
{ {
if (!s_RegisteredProperties.TryGetValue(className, out var prop)) if (!s_RegisteredProperties.TryGetValue(className, out var prop))
s_RegisteredProperties[className] = prop = RegisterClassProxyProperty(className); s_RegisteredProperties[className] = prop = RegisterClassProxyProperty(className);
@ -21,7 +21,7 @@ namespace Avalonia
var prop = AvaloniaProperty.Register<StyledElement, bool>("__AvaloniaReserved::Classes::" + className); var prop = AvaloniaProperty.Register<StyledElement, bool>("__AvaloniaReserved::Classes::" + className);
prop.Changed.Subscribe(args => prop.Changed.Subscribe(args =>
{ {
var classes = ((IStyledElement)args.Sender).Classes; var classes = ((StyledElement)args.Sender).Classes;
classes.Set(className, args.NewValue.GetValueOrDefault()); classes.Set(className, args.NewValue.GetValueOrDefault());
}); });

2
src/Avalonia.Base/Controls/Classes.cs

@ -6,7 +6,7 @@ using Avalonia.Utilities;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
/// <summary> /// <summary>
/// Holds a collection of style classes for an <see cref="IStyledElement"/>. /// Holds a collection of style classes for an <see cref="StyledElement"/>.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Similar to CSS, each control may have any number of styling classes applied. /// Similar to CSS, each control may have any number of styling classes applied.

2
src/Avalonia.Base/Controls/ISetInheritanceParent.cs

@ -17,6 +17,6 @@ namespace Avalonia.Controls
/// Sets the control's inheritance parent. /// Sets the control's inheritance parent.
/// </summary> /// </summary>
/// <param name="parent">The parent.</param> /// <param name="parent">The parent.</param>
void SetParent(IAvaloniaObject? parent); void SetParent(AvaloniaObject? parent);
} }
} }

3
src/Avalonia.Base/Controls/ResourceNodeExtensions.cs

@ -2,6 +2,7 @@
using Avalonia.Data.Converters; using Avalonia.Data.Converters;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Reactive; using Avalonia.Reactive;
using Avalonia.Styling;
#nullable enable #nullable enable
@ -49,7 +50,7 @@ namespace Avalonia.Controls
return true; return true;
} }
current = (current as IStyledElement)?.StylingParent as IResourceNode; current = (current as IStyleHost)?.StylingParent as IResourceNode;
} }
value = null; value = null;

4
src/Avalonia.Base/Data/BindingOperations.cs

@ -9,7 +9,7 @@ namespace Avalonia.Data
public static readonly object DoNothing = new DoNothingType(); public static readonly object DoNothing = new DoNothingType();
/// <summary> /// <summary>
/// Applies an <see cref="InstancedBinding"/> a property on an <see cref="IAvaloniaObject"/>. /// Applies an <see cref="InstancedBinding"/> a property on an <see cref="AvaloniaObject"/>.
/// </summary> /// </summary>
/// <param name="target">The target object.</param> /// <param name="target">The target object.</param>
/// <param name="property">The property to bind.</param> /// <param name="property">The property to bind.</param>
@ -22,7 +22,7 @@ namespace Avalonia.Data
/// </param> /// </param>
/// <returns>An <see cref="IDisposable"/> which can be used to cancel the binding.</returns> /// <returns>An <see cref="IDisposable"/> which can be used to cancel the binding.</returns>
public static IDisposable Apply( public static IDisposable Apply(
IAvaloniaObject target, AvaloniaObject target,
AvaloniaProperty property, AvaloniaProperty property,
InstancedBinding binding, InstancedBinding binding,
object? anchor) object? anchor)

4
src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs

@ -24,7 +24,7 @@ namespace Avalonia.Data.Core
{ {
try try
{ {
if (Target.TryGetTarget(out var target) && target is IAvaloniaObject obj) if (Target.TryGetTarget(out var target) && target is AvaloniaObject obj)
{ {
obj.SetValue(_property, value, priority); obj.SetValue(_property, value, priority);
return true; return true;
@ -39,7 +39,7 @@ namespace Avalonia.Data.Core
protected override void StartListeningCore(WeakReference<object?> reference) protected override void StartListeningCore(WeakReference<object?> reference)
{ {
if (reference.TryGetTarget(out var target) && target is IAvaloniaObject obj) if (reference.TryGetTarget(out var target) && target is AvaloniaObject obj)
{ {
_subscription = new AvaloniaPropertyObservable<object?>(obj, _property).Subscribe(ValueChanged); _subscription = new AvaloniaPropertyObservable<object?>(obj, _property).Subscribe(ValueChanged);
} }

2
src/Avalonia.Base/Data/IBinding.cs

@ -24,7 +24,7 @@ namespace Avalonia.Data
/// A <see cref="InstancedBinding"/> or null if the binding could not be resolved. /// A <see cref="InstancedBinding"/> or null if the binding could not be resolved.
/// </returns> /// </returns>
InstancedBinding? Initiate( InstancedBinding? Initiate(
IAvaloniaObject target, AvaloniaObject target,
AvaloniaProperty? targetProperty, AvaloniaProperty? targetProperty,
object? anchor = null, object? anchor = null,
bool enableDataValidation = false); bool enableDataValidation = false);

6
src/Avalonia.Base/Data/IndexerBinding.cs

@ -3,7 +3,7 @@
public class IndexerBinding : IBinding public class IndexerBinding : IBinding
{ {
public IndexerBinding( public IndexerBinding(
IAvaloniaObject source, AvaloniaObject source,
AvaloniaProperty property, AvaloniaProperty property,
BindingMode mode) BindingMode mode)
{ {
@ -12,12 +12,12 @@
Mode = mode; Mode = mode;
} }
private IAvaloniaObject Source { get; } private AvaloniaObject Source { get; }
public AvaloniaProperty Property { get; } public AvaloniaProperty Property { get; }
private BindingMode Mode { get; } private BindingMode Mode { get; }
public InstancedBinding? Initiate( public InstancedBinding? Initiate(
IAvaloniaObject target, AvaloniaObject target,
AvaloniaProperty? targetProperty, AvaloniaProperty? targetProperty,
object? anchor = null, object? anchor = null,
bool enableDataValidation = false) bool enableDataValidation = false)

2
src/Avalonia.Base/Data/InstancedBinding.cs

@ -9,7 +9,7 @@ namespace Avalonia.Data
/// <remarks> /// <remarks>
/// Whereas an <see cref="IBinding"/> holds a description of a binding such as "Bind to the X /// Whereas an <see cref="IBinding"/> holds a description of a binding such as "Bind to the X
/// property on a control's DataContext"; this class represents a binding that has been /// property on a control's DataContext"; this class represents a binding that has been
/// *instanced* by calling <see cref="IBinding.Initiate(IAvaloniaObject, AvaloniaProperty, object, bool)"/> /// *instanced* by calling <see cref="IBinding.Initiate(AvaloniaObject, AvaloniaProperty, object, bool)"/>
/// on a target object. /// on a target object.
/// </remarks> /// </remarks>
public class InstancedBinding public class InstancedBinding

2
src/Avalonia.Base/Diagnostics/IAvaloniaObjectDebug.cs

@ -8,7 +8,7 @@ namespace Avalonia.Diagnostics
public interface IAvaloniaObjectDebug public interface IAvaloniaObjectDebug
{ {
/// <summary> /// <summary>
/// Gets the subscriber list for the <see cref="IAvaloniaObject.PropertyChanged"/> /// Gets the subscriber list for the <see cref="AvaloniaObject.PropertyChanged"/>
/// event. /// event.
/// </summary> /// </summary>
/// <returns> /// <returns>

10
src/Avalonia.Base/DirectProperty.cs

@ -15,7 +15,7 @@ namespace Avalonia
/// allows the avalonia property system to read and write the current value. /// allows the avalonia property system to read and write the current value.
/// </remarks> /// </remarks>
public class DirectProperty<TOwner, TValue> : DirectPropertyBase<TValue>, IDirectPropertyAccessor public class DirectProperty<TOwner, TValue> : DirectPropertyBase<TValue>, IDirectPropertyAccessor
where TOwner : IAvaloniaObject where TOwner : AvaloniaObject
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="DirectProperty{TOwner, TValue}"/> class. /// Initializes a new instance of the <see cref="DirectProperty{TOwner, TValue}"/> class.
@ -151,13 +151,13 @@ namespace Avalonia
} }
/// <inheritdoc/> /// <inheritdoc/>
internal override TValue InvokeGetter(IAvaloniaObject instance) internal override TValue InvokeGetter(AvaloniaObject instance)
{ {
return Getter((TOwner)instance); return Getter((TOwner)instance);
} }
/// <inheritdoc/> /// <inheritdoc/>
internal override void InvokeSetter(IAvaloniaObject instance, BindingValue<TValue> value) internal override void InvokeSetter(AvaloniaObject instance, BindingValue<TValue> value)
{ {
if (Setter == null) if (Setter == null)
{ {
@ -171,13 +171,13 @@ namespace Avalonia
} }
/// <inheritdoc/> /// <inheritdoc/>
object? IDirectPropertyAccessor.GetValue(IAvaloniaObject instance) object? IDirectPropertyAccessor.GetValue(AvaloniaObject instance)
{ {
return Getter((TOwner)instance); return Getter((TOwner)instance);
} }
/// <inheritdoc/> /// <inheritdoc/>
void IDirectPropertyAccessor.SetValue(IAvaloniaObject instance, object? value) void IDirectPropertyAccessor.SetValue(AvaloniaObject instance, object? value)
{ {
if (Setter == null) if (Setter == null)
{ {

6
src/Avalonia.Base/DirectPropertyBase.cs

@ -54,14 +54,14 @@ namespace Avalonia
/// </summary> /// </summary>
/// <param name="instance">The instance.</param> /// <param name="instance">The instance.</param>
/// <returns>The property value.</returns> /// <returns>The property value.</returns>
internal abstract TValue InvokeGetter(IAvaloniaObject instance); internal abstract TValue InvokeGetter(AvaloniaObject instance);
/// <summary> /// <summary>
/// Sets the value of the property on the instance. /// Sets the value of the property on the instance.
/// </summary> /// </summary>
/// <param name="instance">The instance.</param> /// <param name="instance">The instance.</param>
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
internal abstract void InvokeSetter(IAvaloniaObject instance, BindingValue<TValue> value); internal abstract void InvokeSetter(AvaloniaObject instance, BindingValue<TValue> value);
/// <summary> /// <summary>
/// Gets the unset value for the property on the specified type. /// Gets the unset value for the property on the specified type.
@ -91,7 +91,7 @@ namespace Avalonia
/// </summary> /// </summary>
/// <typeparam name="T">The type.</typeparam> /// <typeparam name="T">The type.</typeparam>
/// <param name="metadata">The metadata.</param> /// <param name="metadata">The metadata.</param>
public void OverrideMetadata<T>(DirectPropertyMetadata<TValue> metadata) where T : IAvaloniaObject public void OverrideMetadata<T>(DirectPropertyMetadata<TValue> metadata) where T : AvaloniaObject
{ {
base.OverrideMetadata(typeof(T), metadata); base.OverrideMetadata(typeof(T), metadata);
} }

79
src/Avalonia.Base/IAvaloniaObject.cs

@ -1,79 +0,0 @@
using System;
using Avalonia.Data;
using Avalonia.Metadata;
namespace Avalonia
{
/// <summary>
/// Interface for getting/setting <see cref="AvaloniaProperty"/> values on an object.
/// </summary>
[NotClientImplementable]
public interface IAvaloniaObject
{
/// <summary>
/// Raised when a <see cref="AvaloniaProperty"/> value changes on this object.
/// </summary>
event EventHandler<AvaloniaPropertyChangedEventArgs>? PropertyChanged;
/// <summary>
/// Clears an <see cref="AvaloniaProperty"/>'s local value.
/// </summary>
/// <param name="property">The property.</param>
void ClearValue(AvaloniaProperty property);
/// <summary>
/// Gets a <see cref="AvaloniaProperty"/> value.
/// </summary>
/// <param name="property">The property.</param>
/// <returns>The value.</returns>
object? GetValue(AvaloniaProperty property);
/// <summary>
/// Checks whether a <see cref="AvaloniaProperty"/> is animating.
/// </summary>
/// <param name="property">The property.</param>
/// <returns>True if the property is animating, otherwise false.</returns>
bool IsAnimating(AvaloniaProperty property);
/// <summary>
/// Checks whether a <see cref="AvaloniaProperty"/> is set on this object.
/// </summary>
/// <param name="property">The property.</param>
/// <returns>True if the property is set, otherwise false.</returns>
bool IsSet(AvaloniaProperty property);
/// <summary>
/// Sets a <see cref="AvaloniaProperty"/> value.
/// </summary>
/// <param name="property">The property.</param>
/// <param name="value">The value.</param>
/// <param name="priority">The priority of the value.</param>
/// <returns>
/// An <see cref="IDisposable"/> if setting the property can be undone, otherwise null.
/// </returns>
IDisposable? SetValue(
AvaloniaProperty property,
object? value,
BindingPriority priority = BindingPriority.LocalValue);
/// <summary>
/// Binds a <see cref="AvaloniaProperty"/> to an observable.
/// </summary>
/// <param name="property">The property.</param>
/// <param name="source">The observable.</param>
/// <param name="priority">The priority of the binding.</param>
/// <returns>
/// A disposable which can be used to terminate the binding.
/// </returns>
IDisposable Bind(
AvaloniaProperty property,
IObservable<object?> source,
BindingPriority priority = BindingPriority.LocalValue);
/// <summary>
/// Coerces the specified <see cref="AvaloniaProperty"/>.
/// </summary>
/// <param name="property">The property.</param>
void CoerceValue(AvaloniaProperty property);
}
}

2
src/Avalonia.Base/IDataContextProvider.cs

@ -6,7 +6,7 @@ namespace Avalonia
/// Defines an element with a data context that can be used for binding. /// Defines an element with a data context that can be used for binding.
/// </summary> /// </summary>
[NotClientImplementable] [NotClientImplementable]
public interface IDataContextProvider : IAvaloniaObject public interface IDataContextProvider
{ {
/// <summary> /// <summary>
/// Gets or sets the element's data context. /// Gets or sets the element's data context.

4
src/Avalonia.Base/IDirectPropertyAccessor.cs

@ -24,13 +24,13 @@ namespace Avalonia
/// </summary> /// </summary>
/// <param name="instance">The instance.</param> /// <param name="instance">The instance.</param>
/// <returns>The property value.</returns> /// <returns>The property value.</returns>
object? GetValue(IAvaloniaObject instance); object? GetValue(AvaloniaObject instance);
/// <summary> /// <summary>
/// Sets the value of the property on the instance. /// Sets the value of the property on the instance.
/// </summary> /// </summary>
/// <param name="instance">The instance.</param> /// <param name="instance">The instance.</param>
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
void SetValue(IAvaloniaObject instance, object? value); void SetValue(AvaloniaObject instance, object? value);
} }
} }

39
src/Avalonia.Base/IStyledElement.cs

@ -1,39 +0,0 @@
using System;
using System.ComponentModel;
using Avalonia.Controls;
using Avalonia.LogicalTree;
using Avalonia.Metadata;
using Avalonia.Styling;
namespace Avalonia
{
[NotClientImplementable]
public interface IStyledElement :
IStyleable,
IStyleHost,
ILogical,
IResourceHost,
IDataContextProvider,
ISupportInitialize
{
/// <summary>
/// Occurs when the control has finished initialization.
/// </summary>
event EventHandler? Initialized;
/// <summary>
/// Gets a value that indicates whether the element has finished initialization.
/// </summary>
bool IsInitialized { get; }
/// <summary>
/// Gets or sets the control's styling classes.
/// </summary>
new Classes Classes { get; set; }
/// <summary>
/// Gets the control's logical parent.
/// </summary>
IStyledElement? Parent { get; }
}
}

4
src/Avalonia.Base/Input/AccessKeyHandler.cs

@ -182,13 +182,13 @@ namespace Avalonia.Input
// find all controls who have registered that access key. // find all controls who have registered that access key.
var text = e.Key.ToString().ToUpper(); var text = e.Key.ToString().ToUpper();
var matches = _registered var matches = _registered
.Where(x => x.Item1 == text && x.Item2.IsEffectivelyVisible) .Where(x => x.Item1 == text && ((Visual)x.Item2).IsEffectivelyVisible)
.Select(x => x.Item2); .Select(x => x.Item2);
// If the menu is open, only match controls in the menu's visual tree. // If the menu is open, only match controls in the menu's visual tree.
if (menuIsOpen) if (menuIsOpen)
{ {
matches = matches.Where(x => x is not null && MainMenu!.IsVisualAncestorOf(x)); matches = matches.Where(x => x is not null && ((Visual)MainMenu!).IsVisualAncestorOf((Visual)x));
} }
var match = matches.FirstOrDefault(); var match = matches.FirstOrDefault();

5
src/Avalonia.Base/Input/DragDropDevice.cs

@ -13,7 +13,8 @@ namespace Avalonia.Input
private Interactive? GetTarget(IInputRoot root, Point local) private Interactive? GetTarget(IInputRoot root, Point local)
{ {
var target = root.InputHitTest(local)?.GetSelfAndVisualAncestors()?.OfType<Interactive>()?.FirstOrDefault(); var hit = root.InputHitTest(local) as Visual;
var target = hit?.GetSelfAndVisualAncestors()?.OfType<Interactive>()?.FirstOrDefault();
if (target != null && DragDrop.GetAllowDrop(target)) if (target != null && DragDrop.GetAllowDrop(target))
return target; return target;
return null; return null;
@ -24,7 +25,7 @@ namespace Avalonia.Input
if (target == null) if (target == null)
return DragDropEffects.None; return DragDropEffects.None;
var p = inputRoot.TranslatePoint(point, target); var p = ((Visual)inputRoot).TranslatePoint(point, target);
if (!p.HasValue) if (!p.HasValue)
return DragDropEffects.None; return DragDropEffects.None;

2
src/Avalonia.Base/Input/DragEventArgs.cs

@ -15,7 +15,7 @@ namespace Avalonia.Input
public KeyModifiers KeyModifiers { get; private set; } public KeyModifiers KeyModifiers { get; private set; }
public Point GetPosition(IVisual relativeTo) public Point GetPosition(Visual relativeTo)
{ {
var point = new Point(0, 0); var point = new Point(0, 0);

17
src/Avalonia.Base/Input/FocusManager.cs

@ -185,7 +185,7 @@ namespace Avalonia.Input
/// </summary> /// </summary>
/// <param name="e">The element.</param> /// <param name="e">The element.</param>
/// <returns>True if the element can be focused.</returns> /// <returns>True if the element can be focused.</returns>
private static bool CanFocus(IInputElement e) => e.Focusable && e.IsEffectivelyEnabled && e.IsVisible; private static bool CanFocus(IInputElement e) => e.Focusable && e.IsEffectivelyEnabled && IsVisible(e);
/// <summary> /// <summary>
/// Gets the focus scope ancestors of the specified control, traversing popups. /// Gets the focus scope ancestors of the specified control, traversing popups.
@ -198,14 +198,15 @@ namespace Avalonia.Input
while (c != null) while (c != null)
{ {
var scope = c as IFocusScope; if (c is IFocusScope scope &&
c is Visual v &&
if (scope != null && c.VisualRoot?.IsVisible == true) v.VisualRoot is Visual root &&
root.IsVisible)
{ {
yield return scope; yield return scope;
} }
c = c.GetVisualParent<IInputElement>() ?? c = (c as Visual)?.GetVisualParent<IInputElement>() ??
((c as IHostedVisualTreeRoot)?.Host as IInputElement); ((c as IHostedVisualTreeRoot)?.Host as IInputElement);
} }
} }
@ -221,11 +222,11 @@ namespace Avalonia.Input
return; return;
var ev = (PointerPressedEventArgs)e; var ev = (PointerPressedEventArgs)e;
var visual = (IVisual)sender; var visual = (Visual)sender;
if (sender == e.Source && ev.GetCurrentPoint(visual).Properties.IsLeftButtonPressed) if (sender == e.Source && ev.GetCurrentPoint(visual).Properties.IsLeftButtonPressed)
{ {
IVisual? element = ev.Pointer?.Captured ?? e.Source as IInputElement; Visual? element = ev.Pointer?.Captured as Visual ?? e.Source as Visual;
while (element != null) while (element != null)
{ {
@ -240,5 +241,7 @@ namespace Avalonia.Input
} }
} }
} }
private static bool IsVisible(IInputElement e) => (e as Visual)?.IsVisible ?? true;
} }
} }

4
src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs

@ -35,8 +35,8 @@ namespace Avalonia.Input.GestureRecognizers
if (_inputElement is ILogical logicalParent && recognizer is ISetLogicalParent logical) if (_inputElement is ILogical logicalParent && recognizer is ISetLogicalParent logical)
{ {
logical.SetParent(logicalParent); logical.SetParent(logicalParent);
if (recognizer is IStyleable styleableRecognizer if (recognizer is StyledElement styleableRecognizer
&& _inputElement is IStyleable styleableParent) && _inputElement is StyledElement styleableParent)
styleableRecognizer.Bind(StyledElement.TemplatedParentProperty, styleableRecognizer.Bind(StyledElement.TemplatedParentProperty,
styleableParent.GetObservable(StyledElement.TemplatedParentProperty)); styleableParent.GetObservable(StyledElement.TemplatedParentProperty));
} }

4
src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs

@ -72,7 +72,7 @@ namespace Avalonia.Input.GestureRecognizers
EndGesture(); EndGesture();
_tracking = e.Pointer; _tracking = e.Pointer;
_gestureId = ScrollGestureEventArgs.GetNextFreeId(); _gestureId = ScrollGestureEventArgs.GetNextFreeId();
_trackedRootPoint = e.GetPosition(_target); _trackedRootPoint = e.GetPosition((Visual?)_target);
} }
} }
@ -86,7 +86,7 @@ namespace Avalonia.Input.GestureRecognizers
{ {
if (e.Pointer == _tracking) if (e.Pointer == _tracking)
{ {
var rootPoint = e.GetPosition(_target); var rootPoint = e.GetPosition((Visual?)_target);
if (!_scrolling) if (!_scrolling)
{ {
if (CanHorizontallyScroll && Math.Abs(_trackedRootPoint.X - rootPoint.X) > ScrollStartDistance) if (CanHorizontallyScroll && Math.Abs(_trackedRootPoint.X - rootPoint.X) > ScrollStartDistance)

33
src/Avalonia.Base/Input/Gestures.cs

@ -43,7 +43,7 @@ namespace Avalonia.Input
RoutedEvent.Register<PointerDeltaEventArgs>( RoutedEvent.Register<PointerDeltaEventArgs>(
"PointerSwipeGesture", RoutingStrategies.Bubble, typeof(Gestures)); "PointerSwipeGesture", RoutingStrategies.Bubble, typeof(Gestures));
private static readonly WeakReference<IInteractive?> s_lastPress = new WeakReference<IInteractive?>(null); private static readonly WeakReference<object?> s_lastPress = new WeakReference<object?>(null);
private static Point s_lastPressPoint; private static Point s_lastPressPoint;
static Gestures() static Gestures()
@ -52,32 +52,32 @@ namespace Avalonia.Input
InputElement.PointerReleasedEvent.RouteFinished.Subscribe(PointerReleased); InputElement.PointerReleasedEvent.RouteFinished.Subscribe(PointerReleased);
} }
public static void AddTappedHandler(IInteractive element, EventHandler<RoutedEventArgs> handler) public static void AddTappedHandler(Interactive element, EventHandler<RoutedEventArgs> handler)
{ {
element.AddHandler(TappedEvent, handler); element.AddHandler(TappedEvent, handler);
} }
public static void AddDoubleTappedHandler(IInteractive element, EventHandler<RoutedEventArgs> handler) public static void AddDoubleTappedHandler(Interactive element, EventHandler<RoutedEventArgs> handler)
{ {
element.AddHandler(DoubleTappedEvent, handler); element.AddHandler(DoubleTappedEvent, handler);
} }
public static void AddRightTappedHandler(IInteractive element, EventHandler<RoutedEventArgs> handler) public static void AddRightTappedHandler(Interactive element, EventHandler<RoutedEventArgs> handler)
{ {
element.AddHandler(RightTappedEvent, handler); element.AddHandler(RightTappedEvent, handler);
} }
public static void RemoveTappedHandler(IInteractive element, EventHandler<RoutedEventArgs> handler) public static void RemoveTappedHandler(Interactive element, EventHandler<RoutedEventArgs> handler)
{ {
element.RemoveHandler(TappedEvent, handler); element.RemoveHandler(TappedEvent, handler);
} }
public static void RemoveDoubleTappedHandler(IInteractive element, EventHandler<RoutedEventArgs> handler) public static void RemoveDoubleTappedHandler(Interactive element, EventHandler<RoutedEventArgs> handler)
{ {
element.RemoveHandler(DoubleTappedEvent, handler); element.RemoveHandler(DoubleTappedEvent, handler);
} }
public static void RemoveRightTappedHandler(IInteractive element, EventHandler<RoutedEventArgs> handler) public static void RemoveRightTappedHandler(Interactive element, EventHandler<RoutedEventArgs> handler)
{ {
element.RemoveHandler(RightTappedEvent, handler); element.RemoveHandler(RightTappedEvent, handler);
} }
@ -92,20 +92,22 @@ namespace Avalonia.Input
if (ev.Route == RoutingStrategies.Bubble) if (ev.Route == RoutingStrategies.Bubble)
{ {
var e = (PointerPressedEventArgs)ev; var e = (PointerPressedEventArgs)ev;
var visual = (IVisual)ev.Source; var visual = (Visual)ev.Source;
if (e.ClickCount % 2 == 1) if (e.ClickCount % 2 == 1)
{ {
s_isDoubleTapped = false; s_isDoubleTapped = false;
s_lastPress.SetTarget(ev.Source); s_lastPress.SetTarget(ev.Source);
s_lastPressPoint = e.GetPosition((IVisual)ev.Source); s_lastPressPoint = e.GetPosition((Visual)ev.Source);
} }
else if (e.ClickCount % 2 == 0 && e.GetCurrentPoint(visual).Properties.IsLeftButtonPressed) else if (e.ClickCount % 2 == 0 && e.GetCurrentPoint(visual).Properties.IsLeftButtonPressed)
{ {
if (s_lastPress.TryGetTarget(out var target) && target == e.Source) if (s_lastPress.TryGetTarget(out var target) &&
target == e.Source &&
e.Source is Interactive i)
{ {
s_isDoubleTapped = true; s_isDoubleTapped = true;
e.Source.RaiseEvent(new TappedEventArgs(DoubleTappedEvent, e)); i.RaiseEvent(new TappedEventArgs(DoubleTappedEvent, e));
} }
} }
} }
@ -119,9 +121,10 @@ namespace Avalonia.Input
if (s_lastPress.TryGetTarget(out var target) && if (s_lastPress.TryGetTarget(out var target) &&
target == e.Source && target == e.Source &&
e.InitialPressMouseButton is MouseButton.Left or MouseButton.Right) e.InitialPressMouseButton is MouseButton.Left or MouseButton.Right &&
e.Source is Interactive i)
{ {
var point = e.GetCurrentPoint((IVisual)target); var point = e.GetCurrentPoint((Visual)target);
var settings = AvaloniaLocator.Current.GetService<IPlatformSettings>(); var settings = AvaloniaLocator.Current.GetService<IPlatformSettings>();
var tapSize = settings?.GetTapSize(point.Pointer.Type) ?? new Size(4, 4); var tapSize = settings?.GetTapSize(point.Pointer.Type) ?? new Size(4, 4);
var tapRect = new Rect(s_lastPressPoint, new Size()) var tapRect = new Rect(s_lastPressPoint, new Size())
@ -131,13 +134,13 @@ namespace Avalonia.Input
{ {
if (e.InitialPressMouseButton == MouseButton.Right) if (e.InitialPressMouseButton == MouseButton.Right)
{ {
e.Source.RaiseEvent(new TappedEventArgs(RightTappedEvent, e)); i.RaiseEvent(new TappedEventArgs(RightTappedEvent, e));
} }
//s_isDoubleTapped needed here to prevent invoking Tapped event when DoubleTapped is called. //s_isDoubleTapped needed here to prevent invoking Tapped event when DoubleTapped is called.
//This behaviour matches UWP behaviour. //This behaviour matches UWP behaviour.
else if (s_isDoubleTapped == false) else if (s_isDoubleTapped == false)
{ {
e.Source.RaiseEvent(new TappedEventArgs(TappedEvent, e)); i.RaiseEvent(new TappedEventArgs(TappedEvent, e));
} }
} }
} }

39
src/Avalonia.Base/Input/IInputElement.cs

@ -2,9 +2,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.VisualTree;
#nullable enable
namespace Avalonia.Input namespace Avalonia.Input
{ {
@ -12,7 +9,7 @@ namespace Avalonia.Input
/// Defines input-related functionality for a control. /// Defines input-related functionality for a control.
/// </summary> /// </summary>
[NotClientImplementable] [NotClientImplementable]
public interface IInputElement : IInteractive, IVisual public interface IInputElement
{ {
/// <summary> /// <summary>
/// Occurs when the control receives focus. /// Occurs when the control receives focus.
@ -93,7 +90,12 @@ namespace Avalonia.Input
/// <see cref="IsEnabled"/> value of this control and its parent controls. /// <see cref="IsEnabled"/> value of this control and its parent controls.
/// </remarks> /// </remarks>
bool IsEffectivelyEnabled { get; } bool IsEffectivelyEnabled { get; }
/// <summary>
/// Gets a value indicating whether this control and all its parents are visible.
/// </summary>
bool IsEffectivelyVisible { get; }
/// <summary> /// <summary>
/// Gets a value indicating whether keyboard focus is anywhere within the element or its visual tree child elements. /// Gets a value indicating whether keyboard focus is anywhere within the element or its visual tree child elements.
/// </summary> /// </summary>
@ -123,5 +125,32 @@ namespace Avalonia.Input
/// Gets the key bindings for the element. /// Gets the key bindings for the element.
/// </summary> /// </summary>
List<KeyBinding> KeyBindings { get; } List<KeyBinding> KeyBindings { get; }
/// <summary>
/// Adds a handler for the specified routed event.
/// </summary>
/// <param name="routedEvent">The routed event.</param>
/// <param name="handler">The handler.</param>
/// <param name="routes">The routing strategies to listen to.</param>
/// <param name="handledEventsToo">Whether handled events should also be listened for.</param>
/// <returns>A disposable that terminates the event subscription.</returns>
void AddHandler(
RoutedEvent routedEvent,
Delegate handler,
RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble,
bool handledEventsToo = false);
/// <summary>
/// Removes a handler for the specified routed event.
/// </summary>
/// <param name="routedEvent">The routed event.</param>
/// <param name="handler">The handler.</param>
void RemoveHandler(RoutedEvent routedEvent, Delegate handler);
/// <summary>
/// Raises a routed event.
/// </summary>
/// <param name="e">The event args.</param>
void RaiseEvent(RoutedEventArgs e);
} }
} }

2
src/Avalonia.Base/Input/IMainMenu.cs

@ -9,7 +9,7 @@ namespace Avalonia.Input
/// Defines the interface for a window's main menu. /// Defines the interface for a window's main menu.
/// </summary> /// </summary>
[NotClientImplementable] [NotClientImplementable]
public interface IMainMenu : IVisual public interface IMainMenu
{ {
/// <summary> /// <summary>
/// Gets a value indicating whether the menu is open. /// Gets a value indicating whether the menu is open.

17
src/Avalonia.Base/Input/InputExtensions.cs

@ -12,7 +12,7 @@ namespace Avalonia.Input
/// </summary> /// </summary>
public static class InputExtensions public static class InputExtensions
{ {
private static readonly Func<IVisual, bool> s_hitTestDelegate = IsHitTestVisible; private static readonly Func<Visual, bool> s_hitTestDelegate = IsHitTestVisible;
/// <summary> /// <summary>
/// Returns the active input elements at a point on an <see cref="IInputElement"/>. /// Returns the active input elements at a point on an <see cref="IInputElement"/>.
@ -26,7 +26,8 @@ namespace Avalonia.Input
{ {
element = element ?? throw new ArgumentNullException(nameof(element)); element = element ?? throw new ArgumentNullException(nameof(element));
return element.GetVisualsAt(p, s_hitTestDelegate).Cast<IInputElement>(); return (element as Visual)?.GetVisualsAt(p, s_hitTestDelegate).Cast<IInputElement>() ??
Enumerable.Empty<IInputElement>();
} }
/// <summary> /// <summary>
@ -39,7 +40,7 @@ namespace Avalonia.Input
{ {
element = element ?? throw new ArgumentNullException(nameof(element)); element = element ?? throw new ArgumentNullException(nameof(element));
return element.GetVisualAt(p, s_hitTestDelegate) as IInputElement; return (element as Visual)?.GetVisualAt(p, s_hitTestDelegate) as IInputElement;
} }
/// <summary> /// <summary>
@ -55,22 +56,22 @@ namespace Avalonia.Input
public static IInputElement? InputHitTest( public static IInputElement? InputHitTest(
this IInputElement element, this IInputElement element,
Point p, Point p,
Func<IVisual, bool> filter) Func<Visual, bool> filter)
{ {
element = element ?? throw new ArgumentNullException(nameof(element)); element = element ?? throw new ArgumentNullException(nameof(element));
filter = filter ?? throw new ArgumentNullException(nameof(filter)); filter = filter ?? throw new ArgumentNullException(nameof(filter));
return element.GetVisualAt(p, x => s_hitTestDelegate(x) && filter(x)) as IInputElement; return (element as Visual)?.GetVisualAt(p, x => s_hitTestDelegate(x) && filter(x)) as IInputElement;
} }
private static bool IsHitTestVisible(IVisual visual) private static bool IsHitTestVisible(Visual visual)
{ {
var element = visual as IInputElement; var element = visual as IInputElement;
return element != null && return element != null &&
element.IsVisible && visual.IsVisible &&
element.IsHitTestVisible && element.IsHitTestVisible &&
element.IsEffectivelyEnabled && element.IsEffectivelyEnabled &&
element.IsAttachedToVisualTree; visual.IsAttachedToVisualTree;
} }
} }
} }

43
src/Avalonia.Base/Input/KeyboardDevice.cs

@ -3,7 +3,6 @@ using System.Runtime.CompilerServices;
using Avalonia.Input.Raw; using Avalonia.Input.Raw;
using Avalonia.Input.TextInput; using Avalonia.Input.TextInput;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.VisualTree;
namespace Avalonia.Input namespace Avalonia.Input
{ {
@ -37,18 +36,21 @@ namespace Avalonia.Input
ie.IsKeyboardFocusWithin = false; ie.IsKeyboardFocusWithin = false;
} }
el = (IInputElement?)el.VisualParent; el = (IInputElement?)(el as Visual)?.VisualParent;
} }
} }
private void ClearFocusWithin(IInputElement element, bool clearRoot) private void ClearFocusWithin(IInputElement element, bool clearRoot)
{ {
foreach (var visual in element.VisualChildren) if (element is Visual v)
{ {
if (visual is IInputElement el && el.IsKeyboardFocusWithin) foreach (var visual in v.VisualChildren)
{ {
ClearFocusWithin(el, true); if (visual is IInputElement el && el.IsKeyboardFocusWithin)
break; {
ClearFocusWithin(el, true);
break;
}
} }
} }
@ -81,7 +83,7 @@ namespace Avalonia.Input
break; break;
} }
el = el.VisualParent as IInputElement; el = (el as Visual)?.VisualParent as IInputElement;
} }
el = oldElement; el = oldElement;
@ -100,18 +102,21 @@ namespace Avalonia.Input
ie.IsKeyboardFocusWithin = true; ie.IsKeyboardFocusWithin = true;
} }
el = el.VisualParent as IInputElement; el = (el as Visual)?.VisualParent as IInputElement;
} }
} }
private void ClearChildrenFocusWithin(IInputElement element, bool clearRoot) private void ClearChildrenFocusWithin(IInputElement element, bool clearRoot)
{ {
foreach (var visual in element.VisualChildren) if (element is Visual v)
{ {
if (visual is IInputElement el && el.IsKeyboardFocusWithin) foreach (var visual in v.VisualChildren)
{ {
ClearChildrenFocusWithin(el, true); if (visual is IInputElement el && el.IsKeyboardFocusWithin)
break; {
ClearChildrenFocusWithin(el, true);
break;
}
} }
} }
@ -128,11 +133,11 @@ namespace Avalonia.Input
{ {
if (element != FocusedElement) if (element != FocusedElement)
{ {
var interactive = FocusedElement as IInteractive; var interactive = FocusedElement as Interactive;
if (FocusedElement != null && if (FocusedElement != null &&
(!FocusedElement.IsAttachedToVisualTree || (!((Visual)FocusedElement).IsAttachedToVisualTree ||
_focusedRoot != element?.VisualRoot as IInputRoot) && _focusedRoot != ((Visual?)element)?.VisualRoot as IInputRoot) &&
_focusedRoot != null) _focusedRoot != null)
{ {
ClearChildrenFocusWithin(_focusedRoot, true); ClearChildrenFocusWithin(_focusedRoot, true);
@ -140,14 +145,14 @@ namespace Avalonia.Input
SetIsFocusWithin(FocusedElement, element); SetIsFocusWithin(FocusedElement, element);
_focusedElement = element; _focusedElement = element;
_focusedRoot = _focusedElement?.VisualRoot as IInputRoot; _focusedRoot = ((Visual?)_focusedElement)?.VisualRoot as IInputRoot;
interactive?.RaiseEvent(new RoutedEventArgs interactive?.RaiseEvent(new RoutedEventArgs
{ {
RoutedEvent = InputElement.LostFocusEvent, RoutedEvent = InputElement.LostFocusEvent,
}); });
interactive = element as IInteractive; interactive = element as Interactive;
interactive?.RaiseEvent(new GotFocusEventArgs interactive?.RaiseEvent(new GotFocusEventArgs
{ {
@ -191,8 +196,8 @@ namespace Avalonia.Input
KeyModifiers = keyInput.Modifiers.ToKeyModifiers(), KeyModifiers = keyInput.Modifiers.ToKeyModifiers(),
Source = element, Source = element,
}; };
IVisual? currentHandler = element; var currentHandler = element as Visual;
while (currentHandler != null && !ev.Handled && keyInput.Type == RawKeyEventType.KeyDown) while (currentHandler != null && !ev.Handled && keyInput.Type == RawKeyEventType.KeyDown)
{ {
var bindings = (currentHandler as IInputElement)?.KeyBindings; var bindings = (currentHandler as IInputElement)?.KeyBindings;

2
src/Avalonia.Base/Input/KeyboardNavigation.cs

@ -68,7 +68,7 @@ namespace Avalonia.Input
/// <param name="value">The tab index.</param> /// <param name="value">The tab index.</param>
public static void SetTabIndex(IInputElement element, int value) public static void SetTabIndex(IInputElement element, int value)
{ {
((IAvaloniaObject)element).SetValue(TabIndexProperty, value); ((AvaloniaObject)element).SetValue(TabIndexProperty, value);
} }
/// <summary> /// <summary>

6
src/Avalonia.Base/Input/KeyboardNavigationHandler.cs

@ -50,7 +50,7 @@ namespace Avalonia.Input
element = element ?? throw new ArgumentNullException(nameof(element)); element = element ?? throw new ArgumentNullException(nameof(element));
// If there's a custom keyboard navigation handler as an ancestor, use that. // If there's a custom keyboard navigation handler as an ancestor, use that.
var custom = element.FindAncestorOfType<ICustomKeyboardNavigation>(true); var custom = (element as Visual)?.FindAncestorOfType<ICustomKeyboardNavigation>(true);
if (custom is object && HandlePreCustomNavigation(custom, element, direction, out var ce)) if (custom is object && HandlePreCustomNavigation(custom, element, direction, out var ce))
return ce; return ce;
@ -156,9 +156,9 @@ namespace Avalonia.Input
NavigationDirection direction, NavigationDirection direction,
[NotNullWhen(true)] out IInputElement? result) [NotNullWhen(true)] out IInputElement? result)
{ {
if (newElement is object) if (newElement is Visual v)
{ {
var customHandler = newElement.FindAncestorOfType<ICustomKeyboardNavigation>(true); var customHandler = v.FindAncestorOfType<ICustomKeyboardNavigation>(true);
if (customHandler is object) if (customHandler is object)
{ {

14
src/Avalonia.Base/Input/MouseDevice.cs

@ -141,7 +141,7 @@ namespace Avalonia.Input
_lastClickRect = new Rect(p, new Size()) _lastClickRect = new Rect(p, new Size())
.Inflate(new Thickness(doubleClickSize.Width / 2, doubleClickSize.Height / 2)); .Inflate(new Thickness(doubleClickSize.Width / 2, doubleClickSize.Height / 2));
_lastMouseDownButton = properties.PointerUpdateKind.GetMouseButton(); _lastMouseDownButton = properties.PointerUpdateKind.GetMouseButton();
var e = new PointerPressedEventArgs(source, _pointer, root, p, timestamp, properties, inputModifiers, _clickCount); var e = new PointerPressedEventArgs(source, _pointer, (Visual)root, p, timestamp, properties, inputModifiers, _clickCount);
source.RaiseEvent(e); source.RaiseEvent(e);
return e.Handled; return e.Handled;
} }
@ -161,7 +161,7 @@ namespace Avalonia.Input
if (source is object) if (source is object)
{ {
var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, root, var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, (Visual)root,
p, timestamp, properties, inputModifiers, intermediatePoints); p, timestamp, properties, inputModifiers, intermediatePoints);
source.RaiseEvent(e); source.RaiseEvent(e);
@ -181,7 +181,7 @@ namespace Avalonia.Input
if (source is not null) if (source is not null)
{ {
var e = new PointerReleasedEventArgs(source, _pointer, root, p, timestamp, props, inputModifiers, var e = new PointerReleasedEventArgs(source, _pointer, (Visual)root, p, timestamp, props, inputModifiers,
_lastMouseDownButton); _lastMouseDownButton);
source?.RaiseEvent(e); source?.RaiseEvent(e);
@ -204,7 +204,7 @@ namespace Avalonia.Input
if (source is not null) if (source is not null)
{ {
var e = new PointerWheelEventArgs(source, _pointer, root, p, timestamp, props, inputModifiers, delta); var e = new PointerWheelEventArgs(source, _pointer, (Visual)root, p, timestamp, props, inputModifiers, delta);
source?.RaiseEvent(e); source?.RaiseEvent(e);
return e.Handled; return e.Handled;
@ -224,7 +224,7 @@ namespace Avalonia.Input
if (source != null) if (source != null)
{ {
var e = new PointerDeltaEventArgs(Gestures.PointerTouchPadGestureMagnifyEvent, source, var e = new PointerDeltaEventArgs(Gestures.PointerTouchPadGestureMagnifyEvent, source,
_pointer, root, p, timestamp, props, inputModifiers, delta); _pointer, (Visual)root, p, timestamp, props, inputModifiers, delta);
source?.RaiseEvent(e); source?.RaiseEvent(e);
return e.Handled; return e.Handled;
@ -244,7 +244,7 @@ namespace Avalonia.Input
if (source != null) if (source != null)
{ {
var e = new PointerDeltaEventArgs(Gestures.PointerTouchPadGestureRotateEvent, source, var e = new PointerDeltaEventArgs(Gestures.PointerTouchPadGestureRotateEvent, source,
_pointer, root, p, timestamp, props, inputModifiers, delta); _pointer, (Visual)root, p, timestamp, props, inputModifiers, delta);
source?.RaiseEvent(e); source?.RaiseEvent(e);
return e.Handled; return e.Handled;
@ -264,7 +264,7 @@ namespace Avalonia.Input
if (source != null) if (source != null)
{ {
var e = new PointerDeltaEventArgs(Gestures.PointerTouchPadGestureSwipeEvent, source, var e = new PointerDeltaEventArgs(Gestures.PointerTouchPadGestureSwipeEvent, source,
_pointer, root, p, timestamp, props, inputModifiers, delta); _pointer, (Visual)root, p, timestamp, props, inputModifiers, delta);
source?.RaiseEvent(e); source?.RaiseEvent(e);
return e.Handled; return e.Handled;

12
src/Avalonia.Base/Input/Navigation/FocusExtensions.cs

@ -10,13 +10,21 @@ namespace Avalonia.Input.Navigation
/// </summary> /// </summary>
/// <param name="e">The element.</param> /// <param name="e">The element.</param>
/// <returns>True if the element can be focused.</returns> /// <returns>True if the element can be focused.</returns>
public static bool CanFocus(this IInputElement e) => e.Focusable && e.IsEffectivelyEnabled && e.IsVisible; public static bool CanFocus(this IInputElement e)
{
var visible = (e as Visual)?.IsVisible ?? true;
return e.Focusable && e.IsEffectivelyEnabled && visible;
}
/// <summary> /// <summary>
/// Checks if descendants of the specified element can be focused. /// Checks if descendants of the specified element can be focused.
/// </summary> /// </summary>
/// <param name="e">The element.</param> /// <param name="e">The element.</param>
/// <returns>True if descendants of the element can be focused.</returns> /// <returns>True if descendants of the element can be focused.</returns>
public static bool CanFocusDescendants(this IInputElement e) => e.IsEffectivelyEnabled && e.IsVisible; public static bool CanFocusDescendants(this IInputElement e)
{
var visible = (e as Visual)?.IsVisible ?? true;
return e.IsEffectivelyEnabled && visible;
}
} }
} }

18
src/Avalonia.Base/Input/Navigation/TabNavigation.cs

@ -212,9 +212,10 @@ namespace Avalonia.Input.Navigation
if (!IsFocusScope(e)) if (!IsFocusScope(e))
{ {
// Verify if focusedElement is a visual descendant of e // Verify if focusedElement is a visual descendant of e
if (focusedElement is IVisual visualFocusedElement && if (focusedElement is Visual visualFocusedElement &&
e is Visual v &&
visualFocusedElement != e && visualFocusedElement != e &&
e.IsVisualAncestorOf(visualFocusedElement)) v.IsVisualAncestorOf(visualFocusedElement))
{ {
return focusedElement; return focusedElement;
} }
@ -236,7 +237,7 @@ namespace Avalonia.Input.Navigation
if (uiElement is null || IsVisibleAndEnabled(uiElement)) if (uiElement is null || IsVisibleAndEnabled(uiElement))
{ {
if (e is IVisual elementAsVisual) if (e is Visual elementAsVisual)
{ {
var children = elementAsVisual.VisualChildren; var children = elementAsVisual.VisualChildren;
var count = children.Count; var count = children.Count;
@ -272,7 +273,7 @@ namespace Avalonia.Input.Navigation
if (uiElement == null || IsVisibleAndEnabled(uiElement)) if (uiElement == null || IsVisibleAndEnabled(uiElement))
{ {
var elementAsVisual = e as IVisual; var elementAsVisual = e as Visual;
if (elementAsVisual != null) if (elementAsVisual != null)
{ {
@ -385,7 +386,7 @@ namespace Avalonia.Input.Navigation
private static IInputElement? GetNextSibling(IInputElement e) private static IInputElement? GetNextSibling(IInputElement e)
{ {
if (GetParent(e) is IVisual parentAsVisual && e is IVisual elementAsVisual) if (GetParent(e) is Visual parentAsVisual && e is Visual elementAsVisual)
{ {
var children = parentAsVisual.VisualChildren; var children = parentAsVisual.VisualChildren;
var count = children.Count; var count = children.Count;
@ -589,7 +590,7 @@ namespace Avalonia.Input.Navigation
private static IInputElement? GetPreviousSibling(IInputElement e) private static IInputElement? GetPreviousSibling(IInputElement e)
{ {
if (GetParent(e) is IVisual parentAsVisual && e is IVisual elementAsVisual) if (GetParent(e) is Visual parentAsVisual && e is Visual elementAsVisual)
{ {
var children = parentAsVisual.VisualChildren; var children = parentAsVisual.VisualChildren;
var count = children.Count; var count = children.Count;
@ -646,7 +647,7 @@ namespace Avalonia.Input.Navigation
private static IInputElement? GetParent(IInputElement e) private static IInputElement? GetParent(IInputElement e)
{ {
// For Visual - go up the visual parent chain until we find Visual. // For Visual - go up the visual parent chain until we find Visual.
if (e is IVisual v) if (e is Visual v)
return v.FindAncestorOfType<IInputElement>(); return v.FindAncestorOfType<IInputElement>();
// This will need to be implemented when we have non-visual input elements. // This will need to be implemented when we have non-visual input elements.
@ -669,6 +670,7 @@ namespace Avalonia.Input.Navigation
} }
private static bool IsTabStopOrGroup(IInputElement e) => IsTabStop(e) || IsGroup(e); private static bool IsTabStopOrGroup(IInputElement e) => IsTabStop(e) || IsGroup(e);
private static bool IsVisibleAndEnabled(IInputElement e) => e.IsVisible && e.IsEnabled; private static bool IsVisible(IInputElement e) => (e as Visual)?.IsVisible ?? true;
private static bool IsVisibleAndEnabled(IInputElement e) => IsVisible(e) && e.IsEnabled;
} }
} }

6
src/Avalonia.Base/Input/PenDevice.cs

@ -91,7 +91,7 @@ namespace Avalonia.Input
_lastClickRect = new Rect(p, new Size()) _lastClickRect = new Rect(p, new Size())
.Inflate(new Thickness(doubleClickSize.Width / 2, doubleClickSize.Height / 2)); .Inflate(new Thickness(doubleClickSize.Width / 2, doubleClickSize.Height / 2));
_lastMouseDownButton = properties.PointerUpdateKind.GetMouseButton(); _lastMouseDownButton = properties.PointerUpdateKind.GetMouseButton();
var e = new PointerPressedEventArgs(source, pointer, root, p, timestamp, properties, inputModifiers, _clickCount); var e = new PointerPressedEventArgs(source, pointer, (Visual)root, p, timestamp, properties, inputModifiers, _clickCount);
source.RaiseEvent(e); source.RaiseEvent(e);
return e.Handled; return e.Handled;
} }
@ -108,7 +108,7 @@ namespace Avalonia.Input
if (source is not null) if (source is not null)
{ {
var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, pointer, root, var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, pointer, (Visual)root,
p, timestamp, properties, inputModifiers, intermediatePoints); p, timestamp, properties, inputModifiers, intermediatePoints);
source.RaiseEvent(e); source.RaiseEvent(e);
@ -126,7 +126,7 @@ namespace Avalonia.Input
if (source is not null) if (source is not null)
{ {
var e = new PointerReleasedEventArgs(source, pointer, root, p, timestamp, properties, inputModifiers, var e = new PointerReleasedEventArgs(source, pointer, (Visual)root, p, timestamp, properties, inputModifiers,
_lastMouseDownButton); _lastMouseDownButton);
source?.RaiseEvent(e); source?.RaiseEvent(e);

20
src/Avalonia.Base/Input/Pointer.cs

@ -21,10 +21,10 @@ namespace Avalonia.Input
IInputElement? FindCommonParent(IInputElement? control1, IInputElement? control2) IInputElement? FindCommonParent(IInputElement? control1, IInputElement? control2)
{ {
if (control1 == null || control2 == null) if (control1 is not Visual c1 || control2 is not Visual c2)
return null; return null;
var seen = new HashSet<IInputElement>(control1.GetSelfAndVisualAncestors().OfType<IInputElement>()); var seen = new HashSet<IInputElement>(c1.GetSelfAndVisualAncestors().OfType<IInputElement>());
return control2.GetSelfAndVisualAncestors().OfType<IInputElement>().FirstOrDefault(seen.Contains); return c2.GetSelfAndVisualAncestors().OfType<IInputElement>().FirstOrDefault(seen.Contains);
} }
protected virtual void PlatformCapture(IInputElement? element) protected virtual void PlatformCapture(IInputElement? element)
@ -34,15 +34,15 @@ namespace Avalonia.Input
public void Capture(IInputElement? control) public void Capture(IInputElement? control)
{ {
if (Captured != null) if (Captured is Visual v1)
Captured.DetachedFromVisualTree -= OnCaptureDetached; v1.DetachedFromVisualTree -= OnCaptureDetached;
var oldCapture = Captured; var oldCapture = Captured;
Captured = control; Captured = control;
PlatformCapture(control); PlatformCapture(control);
if (oldCapture != null) if (oldCapture is Visual v2)
{ {
var commonParent = FindCommonParent(control, oldCapture); var commonParent = FindCommonParent(control, oldCapture);
foreach (var notifyTarget in oldCapture.GetSelfAndVisualAncestors().OfType<IInputElement>()) foreach (var notifyTarget in v2.GetSelfAndVisualAncestors().OfType<IInputElement>())
{ {
if (notifyTarget == commonParent) if (notifyTarget == commonParent)
break; break;
@ -50,11 +50,11 @@ namespace Avalonia.Input
} }
} }
if (Captured != null) if (Captured is Visual v3)
Captured.DetachedFromVisualTree += OnCaptureDetached; v3.DetachedFromVisualTree += OnCaptureDetached;
} }
IInputElement? GetNextCapture(IVisual parent) IInputElement? GetNextCapture(Visual parent)
{ {
return parent as IInputElement ?? parent.FindAncestorOfType<IInputElement>(); return parent as IInputElement ?? parent.FindAncestorOfType<IInputElement>();
} }

4
src/Avalonia.Base/Input/PointerDeltaEventArgs.cs

@ -7,8 +7,8 @@ namespace Avalonia.Input
{ {
public Vector Delta { get; set; } public Vector Delta { get; set; }
internal PointerDeltaEventArgs(RoutedEvent routedEvent, IInteractive? source, internal PointerDeltaEventArgs(RoutedEvent routedEvent, object? source,
IPointer pointer, IVisual rootVisual, Point rootVisualPosition, ulong timestamp, IPointer pointer, Visual rootVisual, Point rootVisualPosition, ulong timestamp,
PointerPointProperties properties, KeyModifiers modifiers, Vector delta) PointerPointProperties properties, KeyModifiers modifiers, Vector delta)
: base(routedEvent, source, pointer, rootVisual, rootVisualPosition, : base(routedEvent, source, pointer, rootVisual, rootVisualPosition,
timestamp, properties, modifiers) timestamp, properties, modifiers)

30
src/Avalonia.Base/Input/PointerEventArgs.cs

@ -8,15 +8,15 @@ namespace Avalonia.Input
{ {
public class PointerEventArgs : RoutedEventArgs public class PointerEventArgs : RoutedEventArgs
{ {
private readonly IVisual? _rootVisual; private readonly Visual? _rootVisual;
private readonly Point _rootVisualPosition; private readonly Point _rootVisualPosition;
private readonly PointerPointProperties _properties; private readonly PointerPointProperties _properties;
private readonly Lazy<IReadOnlyList<RawPointerPoint>?>? _previousPoints; private readonly Lazy<IReadOnlyList<RawPointerPoint>?>? _previousPoints;
internal PointerEventArgs(RoutedEvent routedEvent, internal PointerEventArgs(RoutedEvent routedEvent,
IInteractive? source, object? source,
IPointer pointer, IPointer pointer,
IVisual? rootVisual, Point rootVisualPosition, Visual? rootVisual, Point rootVisualPosition,
ulong timestamp, ulong timestamp,
PointerPointProperties properties, PointerPointProperties properties,
KeyModifiers modifiers) KeyModifiers modifiers)
@ -30,11 +30,11 @@ namespace Avalonia.Input
Timestamp = timestamp; Timestamp = timestamp;
KeyModifiers = modifiers; KeyModifiers = modifiers;
} }
internal PointerEventArgs(RoutedEvent routedEvent, internal PointerEventArgs(RoutedEvent routedEvent,
IInteractive? source, object? source,
IPointer pointer, IPointer pointer,
IVisual? rootVisual, Point rootVisualPosition, Visual? rootVisual, Point rootVisualPosition,
ulong timestamp, ulong timestamp,
PointerPointProperties properties, PointerPointProperties properties,
KeyModifiers modifiers, KeyModifiers modifiers,
@ -59,7 +59,7 @@ namespace Avalonia.Input
/// </summary> /// </summary>
public KeyModifiers KeyModifiers { get; } public KeyModifiers KeyModifiers { get; }
private Point GetPosition(Point pt, IVisual? relativeTo) private Point GetPosition(Point pt, Visual? relativeTo)
{ {
if (_rootVisual == null) if (_rootVisual == null)
return default; return default;
@ -74,14 +74,14 @@ namespace Avalonia.Input
/// </summary> /// </summary>
/// <param name="relativeTo">The control.</param> /// <param name="relativeTo">The control.</param>
/// <returns>The pointer position in the control's coordinates.</returns> /// <returns>The pointer position in the control's coordinates.</returns>
public Point GetPosition(IVisual? relativeTo) => GetPosition(_rootVisualPosition, relativeTo); public Point GetPosition(Visual? relativeTo) => GetPosition(_rootVisualPosition, relativeTo);
/// <summary> /// <summary>
/// Returns the PointerPoint associated with the current event /// Returns the PointerPoint associated with the current event
/// </summary> /// </summary>
/// <param name="relativeTo">The visual which coordinate system to use. Pass null for toplevel coordinate system</param> /// <param name="relativeTo">The visual which coordinate system to use. Pass null for toplevel coordinate system</param>
/// <returns></returns> /// <returns></returns>
public PointerPoint GetCurrentPoint(IVisual? relativeTo) public PointerPoint GetCurrentPoint(Visual? relativeTo)
=> new PointerPoint(Pointer, GetPosition(relativeTo), _properties); => new PointerPoint(Pointer, GetPosition(relativeTo), _properties);
/// <summary> /// <summary>
@ -89,7 +89,7 @@ namespace Avalonia.Input
/// </summary> /// </summary>
/// <param name="relativeTo">The visual which coordinate system to use. Pass null for toplevel coordinate system</param> /// <param name="relativeTo">The visual which coordinate system to use. Pass null for toplevel coordinate system</param>
/// <returns></returns> /// <returns></returns>
public IReadOnlyList<PointerPoint> GetIntermediatePoints(IVisual? relativeTo) public IReadOnlyList<PointerPoint> GetIntermediatePoints(Visual? relativeTo)
{ {
var previousPoints = _previousPoints?.Value; var previousPoints = _previousPoints?.Value;
if (previousPoints == null || previousPoints.Count == 0) if (previousPoints == null || previousPoints.Count == 0)
@ -125,9 +125,9 @@ namespace Avalonia.Input
public class PointerPressedEventArgs : PointerEventArgs public class PointerPressedEventArgs : PointerEventArgs
{ {
internal PointerPressedEventArgs( internal PointerPressedEventArgs(
IInteractive source, object source,
IPointer pointer, IPointer pointer,
IVisual rootVisual, Point rootVisualPosition, Visual rootVisual, Point rootVisualPosition,
ulong timestamp, ulong timestamp,
PointerPointProperties properties, PointerPointProperties properties,
KeyModifiers modifiers, KeyModifiers modifiers,
@ -144,8 +144,8 @@ namespace Avalonia.Input
public class PointerReleasedEventArgs : PointerEventArgs public class PointerReleasedEventArgs : PointerEventArgs
{ {
internal PointerReleasedEventArgs( internal PointerReleasedEventArgs(
IInteractive source, IPointer pointer, object source, IPointer pointer,
IVisual rootVisual, Point rootVisualPosition, ulong timestamp, Visual rootVisual, Point rootVisualPosition, ulong timestamp,
PointerPointProperties properties, KeyModifiers modifiers, PointerPointProperties properties, KeyModifiers modifiers,
MouseButton initialPressMouseButton) MouseButton initialPressMouseButton)
: base(InputElement.PointerReleasedEvent, source, pointer, rootVisual, rootVisualPosition, : base(InputElement.PointerReleasedEvent, source, pointer, rootVisual, rootVisualPosition,
@ -164,7 +164,7 @@ namespace Avalonia.Input
{ {
public IPointer Pointer { get; } public IPointer Pointer { get; }
internal PointerCaptureLostEventArgs(IInteractive source, IPointer pointer) : base(InputElement.PointerCaptureLostEvent) internal PointerCaptureLostEventArgs(object source, IPointer pointer) : base(InputElement.PointerCaptureLostEvent)
{ {
Pointer = pointer; Pointer = pointer;
Source = source; Source = source;

48
src/Avalonia.Base/Input/PointerOverPreProcessor.cs

@ -44,7 +44,7 @@ namespace Avalonia.Input
&& _lastPointer is (var lastPointer, var lastPosition)) && _lastPointer is (var lastPointer, var lastPosition))
{ {
_lastPointer = null; _lastPointer = null;
ClearPointerOver(lastPointer, args.Root, 0, args.Root.PointToClient(lastPosition), ClearPointerOver(lastPointer, args.Root, 0, PointToClient(args.Root, lastPosition),
new PointerPointProperties(args.InputModifiers, args.Type.ToUpdateKind()), new PointerPointProperties(args.InputModifiers, args.Type.ToUpdateKind()),
args.InputModifiers.ToKeyModifiers()); args.InputModifiers.ToKeyModifiers());
} }
@ -64,14 +64,14 @@ namespace Avalonia.Input
{ {
if (_lastPointer is (var pointer, var position)) if (_lastPointer is (var pointer, var position))
{ {
var clientPoint = _inputRoot.PointToClient(position); var clientPoint = PointToClient(_inputRoot, position);
if (dirtyRect.Contains(clientPoint)) if (dirtyRect.Contains(clientPoint))
{ {
var element = pointer.Captured ?? _inputRoot.InputHitTest(clientPoint); var element = pointer.Captured ?? _inputRoot.InputHitTest(clientPoint);
SetPointerOver(pointer, _inputRoot, element, 0, clientPoint, PointerPointProperties.None, KeyModifiers.None); SetPointerOver(pointer, _inputRoot, element, 0, clientPoint, PointerPointProperties.None, KeyModifiers.None);
} }
else if (!_inputRoot.Bounds.Contains(clientPoint)) else if (!((Visual)_inputRoot).Bounds.Contains(clientPoint))
{ {
ClearPointerOver(pointer, _inputRoot, 0, clientPoint, PointerPointProperties.None, KeyModifiers.None); ClearPointerOver(pointer, _inputRoot, 0, clientPoint, PointerPointProperties.None, KeyModifiers.None);
} }
@ -82,7 +82,7 @@ namespace Avalonia.Input
{ {
if (_lastPointer is (var pointer, var position)) if (_lastPointer is (var pointer, var position))
{ {
var clientPoint = _inputRoot.PointToClient(position); var clientPoint = PointToClient(_inputRoot, position);
ClearPointerOver(pointer, _inputRoot, 0, clientPoint, PointerPointProperties.None, KeyModifiers.None); ClearPointerOver(pointer, _inputRoot, 0, clientPoint, PointerPointProperties.None, KeyModifiers.None);
} }
_lastPointer = null; _lastPointer = null;
@ -101,10 +101,10 @@ namespace Avalonia.Input
// Do not pass rootVisual, when we have unknown position, // Do not pass rootVisual, when we have unknown position,
// so GetPosition won't return invalid values. // so GetPosition won't return invalid values.
var e = new PointerEventArgs(InputElement.PointerExitedEvent, element, pointer, var e = new PointerEventArgs(InputElement.PointerExitedEvent, element, pointer,
position.HasValue ? root : null, position.HasValue ? position.Value : default, position.HasValue ? root as Visual : null, position.HasValue ? position.Value : default,
timestamp, properties, inputModifiers); timestamp, properties, inputModifiers);
if (element != null && !element.IsAttachedToVisualTree) if (element is Visual v && !v.IsAttachedToVisualTree)
{ {
// element has been removed from visual tree so do top down cleanup // element has been removed from visual tree so do top down cleanup
if (root.IsPointerOver) if (root.IsPointerOver)
@ -117,7 +117,7 @@ namespace Avalonia.Input
e.Source = element; e.Source = element;
e.Handled = false; e.Handled = false;
element.RaiseEvent(e); element.RaiseEvent(e);
element = (IInputElement?)element.VisualParent; element = GetVisualParent(element);
} }
root.PointerOverElement = null; root.PointerOverElement = null;
@ -127,14 +127,18 @@ namespace Avalonia.Input
private void ClearChildrenPointerOver(PointerEventArgs e, IInputElement element, bool clearRoot) private void ClearChildrenPointerOver(PointerEventArgs e, IInputElement element, bool clearRoot)
{ {
foreach (IInputElement el in element.VisualChildren) if (element is Visual v)
{ {
if (el.IsPointerOver) foreach (IInputElement el in v.VisualChildren)
{ {
ClearChildrenPointerOver(e, el, true); if (el.IsPointerOver)
break; {
ClearChildrenPointerOver(e, el, true);
break;
}
} }
} }
if (clearRoot) if (clearRoot)
{ {
e.Source = element; e.Source = element;
@ -160,7 +164,7 @@ namespace Avalonia.Input
} }
} }
_lastPointer = (pointer, root.PointToScreen(position)); _lastPointer = (pointer, ((Visual)root).PointToScreen(position));
} }
private void SetPointerOverToElement(IPointer pointer, IInputRoot root, IInputElement element, private void SetPointerOverToElement(IPointer pointer, IInputRoot root, IInputElement element,
@ -177,14 +181,14 @@ namespace Avalonia.Input
branch = el; branch = el;
break; break;
} }
el = (IInputElement?)el.VisualParent; el = GetVisualParent(el);
} }
el = root.PointerOverElement; el = root.PointerOverElement;
var e = new PointerEventArgs(InputElement.PointerExitedEvent, el, pointer, root, position, var e = new PointerEventArgs(InputElement.PointerExitedEvent, el, pointer, (Visual)root, position,
timestamp, properties, inputModifiers); timestamp, properties, inputModifiers);
if (el != null && branch != null && !el.IsAttachedToVisualTree) if (el is Visual v && branch != null && !v.IsAttachedToVisualTree)
{ {
ClearChildrenPointerOver(e, branch, false); ClearChildrenPointerOver(e, branch, false);
} }
@ -194,7 +198,7 @@ namespace Avalonia.Input
e.Source = el; e.Source = el;
e.Handled = false; e.Handled = false;
el.RaiseEvent(e); el.RaiseEvent(e);
el = (IInputElement?)el.VisualParent; el = GetVisualParent(el);
} }
el = root.PointerOverElement = element; el = root.PointerOverElement = element;
@ -206,8 +210,18 @@ namespace Avalonia.Input
e.Source = el; e.Source = el;
e.Handled = false; e.Handled = false;
el.RaiseEvent(e); el.RaiseEvent(e);
el = (IInputElement?)el.VisualParent; el = GetVisualParent(el);
} }
} }
private static IInputElement? GetVisualParent(IInputElement e)
{
return (e as Visual)?.VisualParent as IInputElement;
}
private static Point PointToClient(IInputRoot root, PixelPoint p)
{
return ((Visual)root).PointToClient(p);
}
} }
} }

2
src/Avalonia.Base/Input/PointerWheelEventArgs.cs

@ -7,7 +7,7 @@ namespace Avalonia.Input
{ {
public Vector Delta { get; set; } public Vector Delta { get; set; }
internal PointerWheelEventArgs(IInteractive source, IPointer pointer, IVisual rootVisual, internal PointerWheelEventArgs(object source, IPointer pointer, Visual rootVisual,
Point rootVisualPosition, ulong timestamp, Point rootVisualPosition, ulong timestamp,
PointerPointProperties properties, KeyModifiers modifiers, Vector delta) PointerPointProperties properties, KeyModifiers modifiers, Vector delta)
: base(InputElement.PointerWheelChangedEvent, source, pointer, rootVisual, rootVisualPosition, : base(InputElement.PointerWheelChangedEvent, source, pointer, rootVisual, rootVisualPosition,

2
src/Avalonia.Base/Input/TappedEventArgs.cs

@ -17,6 +17,6 @@ namespace Avalonia.Input
public KeyModifiers KeyModifiers => lastPointerEventArgs.KeyModifiers; public KeyModifiers KeyModifiers => lastPointerEventArgs.KeyModifiers;
public ulong Timestamp => lastPointerEventArgs.Timestamp; public ulong Timestamp => lastPointerEventArgs.Timestamp;
public Point GetPosition(IVisual? relativeTo) => lastPointerEventArgs.GetPosition(relativeTo); public Point GetPosition(Visual? relativeTo) => lastPointerEventArgs.GetPosition(relativeTo);
} }
} }

2
src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs

@ -16,7 +16,7 @@ namespace Avalonia.Input.TextInput
/// <summary> /// <summary>
/// The visual that's showing the text /// The visual that's showing the text
/// </summary> /// </summary>
IVisual TextViewVisual { get; } Visual TextViewVisual { get; }
/// <summary> /// <summary>
/// Should be fired when text-hosting visual is changed /// Should be fired when text-hosting visual is changed
/// </summary> /// </summary>

9
src/Avalonia.Base/Input/TextInput/InputMethodManager.cs

@ -73,10 +73,13 @@ namespace Avalonia.Input.TextInput
private void UpdateCursorRect() private void UpdateCursorRect()
{ {
if (_im == null || _client == null || _focusedElement?.VisualRoot == null) if (_im == null ||
_client == null ||
_focusedElement is not Visual v ||
v.VisualRoot is not Visual root)
return; return;
var transform = _focusedElement.TransformToVisual(_focusedElement.VisualRoot); var transform = v.TransformToVisual(root);
if (transform == null) if (transform == null)
_im.SetCursorRect(default); _im.SetCursorRect(default);
else else
@ -95,7 +98,7 @@ namespace Avalonia.Input.TextInput
return; return;
_focusedElement = element; _focusedElement = element;
var inputMethod = (element?.VisualRoot as ITextInputMethodRoot)?.InputMethod; var inputMethod = ((element as Visual)?.VisualRoot as ITextInputMethodRoot)?.InputMethod;
if (_im != inputMethod) if (_im != inputMethod)
{ {

6
src/Avalonia.Base/Input/TextInput/TransformTrackingHelper.cs

@ -7,7 +7,7 @@ namespace Avalonia.Input.TextInput
{ {
class TransformTrackingHelper : IDisposable class TransformTrackingHelper : IDisposable
{ {
private IVisual? _visual; private Visual? _visual;
private bool _queuedForUpdate; private bool _queuedForUpdate;
private readonly EventHandler<AvaloniaPropertyChangedEventArgs> _propertyChangedHandler; private readonly EventHandler<AvaloniaPropertyChangedEventArgs> _propertyChangedHandler;
private readonly List<Visual> _propertyChangedSubscriptions = new List<Visual>(); private readonly List<Visual> _propertyChangedSubscriptions = new List<Visual>();
@ -17,7 +17,7 @@ namespace Avalonia.Input.TextInput
_propertyChangedHandler = PropertyChangedHandler; _propertyChangedHandler = PropertyChangedHandler;
} }
public void SetVisual(IVisual? visual) public void SetVisual(Visual? visual)
{ {
Dispose(); Dispose();
_visual = visual; _visual = visual;
@ -72,7 +72,7 @@ namespace Avalonia.Input.TextInput
{ {
Matrix? matrix = null; Matrix? matrix = null;
if (_visual != null && _visual.VisualRoot != null) if (_visual != null && _visual.VisualRoot != null)
matrix = _visual.TransformToVisual(_visual.VisualRoot); matrix = _visual.TransformToVisual((Visual)_visual.VisualRoot);
if (Matrix != matrix) if (Matrix != matrix)
{ {
Matrix = matrix; Matrix = matrix;

6
src/Avalonia.Base/Input/TouchDevice.cs

@ -75,7 +75,7 @@ namespace Avalonia.Input
} }
target.RaiseEvent(new PointerPressedEventArgs(target, pointer, target.RaiseEvent(new PointerPressedEventArgs(target, pointer,
args.Root, args.Position, ev.Timestamp, (Visual)args.Root, args.Position, ev.Timestamp,
new PointerPointProperties(GetModifiers(args.InputModifiers, true), updateKind), new PointerPointProperties(GetModifiers(args.InputModifiers, true), updateKind),
keyModifier, _clickCount)); keyModifier, _clickCount));
} }
@ -86,7 +86,7 @@ namespace Avalonia.Input
using (pointer) using (pointer)
{ {
target.RaiseEvent(new PointerReleasedEventArgs(target, pointer, target.RaiseEvent(new PointerReleasedEventArgs(target, pointer,
args.Root, args.Position, ev.Timestamp, (Visual)args.Root, args.Position, ev.Timestamp,
new PointerPointProperties(GetModifiers(args.InputModifiers, false), updateKind), new PointerPointProperties(GetModifiers(args.InputModifiers, false), updateKind),
keyModifier, MouseButton.Left)); keyModifier, MouseButton.Left));
} }
@ -101,7 +101,7 @@ namespace Avalonia.Input
if (args.Type == RawPointerEventType.TouchUpdate) if (args.Type == RawPointerEventType.TouchUpdate)
{ {
target.RaiseEvent(new PointerEventArgs(InputElement.PointerMovedEvent, target, pointer, args.Root, target.RaiseEvent(new PointerEventArgs(InputElement.PointerMovedEvent, target, pointer, (Visual)args.Root,
args.Position, ev.Timestamp, args.Position, ev.Timestamp,
new PointerPointProperties(GetModifiers(args.InputModifiers, true), updateKind), new PointerPointProperties(GetModifiers(args.InputModifiers, true), updateKind),
keyModifier, args.IntermediatePoints)); keyModifier, args.IntermediatePoints));

12
src/Avalonia.Base/Interactivity/EventRoute.cs

@ -43,7 +43,7 @@ namespace Avalonia.Interactivity
/// `DynamicInvoke` on the handler. /// `DynamicInvoke` on the handler.
/// </param> /// </param>
public void Add( public void Add(
IInteractive target, Interactive target,
Delegate handler, Delegate handler,
RoutingStrategies routes, RoutingStrategies routes,
bool handledEventsToo = false, bool handledEventsToo = false,
@ -60,7 +60,7 @@ namespace Avalonia.Interactivity
/// Adds a class handler to the route. /// Adds a class handler to the route.
/// </summary> /// </summary>
/// <param name="target">The target on which the event should be raised.</param> /// <param name="target">The target on which the event should be raised.</param>
public void AddClassHandler(IInteractive target) public void AddClassHandler(Interactive target)
{ {
target = target ?? throw new ArgumentNullException(nameof(target)); target = target ?? throw new ArgumentNullException(nameof(target));
@ -73,7 +73,7 @@ namespace Avalonia.Interactivity
/// </summary> /// </summary>
/// <param name="source">The event source.</param> /// <param name="source">The event source.</param>
/// <param name="e">The event args.</param> /// <param name="e">The event args.</param>
public void RaiseEvent(IInteractive source, RoutedEventArgs e) public void RaiseEvent(Interactive source, RoutedEventArgs e)
{ {
source = source ?? throw new ArgumentNullException(nameof(source)); source = source ?? throw new ArgumentNullException(nameof(source));
e = e ?? throw new ArgumentNullException(nameof(e)); e = e ?? throw new ArgumentNullException(nameof(e));
@ -125,7 +125,7 @@ namespace Avalonia.Interactivity
throw new ArgumentException("Event source may not be null", nameof(e)); throw new ArgumentException("Event source may not be null", nameof(e));
} }
IInteractive? lastTarget = null; Interactive? lastTarget = null;
var start = 0; var start = 0;
var end = _route.Count; var end = _route.Count;
var step = 1; var step = 1;
@ -177,7 +177,7 @@ namespace Avalonia.Interactivity
private readonly struct RouteItem private readonly struct RouteItem
{ {
public RouteItem( public RouteItem(
IInteractive target, Interactive target,
Delegate? handler, Delegate? handler,
Action<Delegate, object, RoutedEventArgs>? adapter, Action<Delegate, object, RoutedEventArgs>? adapter,
RoutingStrategies routes, RoutingStrategies routes,
@ -190,7 +190,7 @@ namespace Avalonia.Interactivity
HandledEventsToo = handledEventsToo; HandledEventsToo = handledEventsToo;
} }
public IInteractive Target { get; } public Interactive Target { get; }
public Delegate? Handler { get; } public Delegate? Handler { get; }
public Action<Delegate, object, RoutedEventArgs>? Adapter { get; } public Action<Delegate, object, RoutedEventArgs>? Adapter { get; }
public RoutingStrategies Routes { get; } public RoutingStrategies Routes { get; }

53
src/Avalonia.Base/Interactivity/IInteractive.cs

@ -1,53 +0,0 @@
using System;
using Avalonia.Metadata;
#nullable enable
namespace Avalonia.Interactivity
{
/// <summary>
/// Interface for objects that raise routed events.
/// </summary>
[NotClientImplementable]
public interface IInteractive
{
/// <summary>
/// Gets the interactive parent of the object for bubbling and tunneling events.
/// </summary>
IInteractive? InteractiveParent { get; }
/// <summary>
/// Adds a handler for the specified routed event.
/// </summary>
/// <param name="routedEvent">The routed event.</param>
/// <param name="handler">The handler.</param>
/// <param name="routes">The routing strategies to listen to.</param>
/// <param name="handledEventsToo">Whether handled events should also be listened for.</param>
/// <returns>A disposable that terminates the event subscription.</returns>
void AddHandler(
RoutedEvent routedEvent,
Delegate handler,
RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble,
bool handledEventsToo = false);
/// <summary>
/// Removes a handler for the specified routed event.
/// </summary>
/// <param name="routedEvent">The routed event.</param>
/// <param name="handler">The handler.</param>
void RemoveHandler(RoutedEvent routedEvent, Delegate handler);
/// <summary>
/// Adds the object's handlers for a routed event to an event route.
/// </summary>
/// <param name="routedEvent">The event.</param>
/// <param name="route">The event route.</param>
void AddToEventRoute(RoutedEvent routedEvent, EventRoute route);
/// <summary>
/// Raises a routed event.
/// </summary>
/// <param name="e">The event args.</param>
void RaiseEvent(RoutedEventArgs e);
}
}

40
src/Avalonia.Base/Interactivity/Interactive.cs

@ -10,14 +10,14 @@ namespace Avalonia.Interactivity
/// <summary> /// <summary>
/// Base class for objects that raise routed events. /// Base class for objects that raise routed events.
/// </summary> /// </summary>
public class Interactive : Layoutable, IInteractive public class Interactive : Layoutable
{ {
private Dictionary<RoutedEvent, List<EventSubscription>>? _eventHandlers; private Dictionary<RoutedEvent, List<EventSubscription>>? _eventHandlers;
/// <summary> /// <summary>
/// Gets the interactive parent of the object for bubbling and tunneling events. /// Gets the interactive parent of the object for bubbling and tunneling events.
/// </summary> /// </summary>
IInteractive? IInteractive.InteractiveParent => ((IVisual)this).VisualParent as IInteractive; protected internal virtual Interactive? InteractiveParent => VisualParent as Interactive;
/// <summary> /// <summary>
/// Adds a handler for the specified routed event. /// Adds a handler for the specified routed event.
@ -125,21 +125,6 @@ namespace Avalonia.Interactivity
route.RaiseEvent(this, e); route.RaiseEvent(this, e);
} }
void IInteractive.AddToEventRoute(RoutedEvent routedEvent, EventRoute route)
{
routedEvent = routedEvent ?? throw new ArgumentNullException(nameof(routedEvent));
route = route ?? throw new ArgumentNullException(nameof(route));
if (_eventHandlers != null &&
_eventHandlers.TryGetValue(routedEvent, out var subscriptions))
{
foreach (var sub in subscriptions)
{
route.Add(this, sub.Handler, sub.Routes, sub.HandledEventsToo, sub.InvokeAdapter);
}
}
}
/// <summary> /// <summary>
/// Builds an event route for a routed event. /// Builds an event route for a routed event.
/// </summary> /// </summary>
@ -151,7 +136,7 @@ namespace Avalonia.Interactivity
/// and should be avoided if there are no handlers for an event. In these cases you can call /// and should be avoided if there are no handlers for an event. In these cases you can call
/// this method to build the event route and check the <see cref="EventRoute.HasHandlers"/> /// this method to build the event route and check the <see cref="EventRoute.HasHandlers"/>
/// property to see if there are any handlers registered on the route. If there are, call /// property to see if there are any handlers registered on the route. If there are, call
/// <see cref="EventRoute.RaiseEvent(IInteractive, RoutedEventArgs)"/> to raise the event. /// <see cref="EventRoute.RaiseEvent(Interactive, RoutedEventArgs)"/> to raise the event.
/// </remarks> /// </remarks>
protected EventRoute BuildEventRoute(RoutedEvent e) protected EventRoute BuildEventRoute(RoutedEvent e)
{ {
@ -163,7 +148,7 @@ namespace Avalonia.Interactivity
if (e.RoutingStrategies.HasAllFlags(RoutingStrategies.Bubble) || if (e.RoutingStrategies.HasAllFlags(RoutingStrategies.Bubble) ||
e.RoutingStrategies.HasAllFlags(RoutingStrategies.Tunnel)) e.RoutingStrategies.HasAllFlags(RoutingStrategies.Tunnel))
{ {
IInteractive? element = this; Interactive? element = this;
while (element != null) while (element != null)
{ {
@ -183,7 +168,7 @@ namespace Avalonia.Interactivity
result.AddClassHandler(this); result.AddClassHandler(this);
} }
((IInteractive)this).AddToEventRoute(e, result); AddToEventRoute(e, result);
} }
return result; return result;
@ -202,6 +187,21 @@ namespace Avalonia.Interactivity
subscriptions.Add(subscription); subscriptions.Add(subscription);
} }
private void AddToEventRoute(RoutedEvent routedEvent, EventRoute route)
{
routedEvent = routedEvent ?? throw new ArgumentNullException(nameof(routedEvent));
route = route ?? throw new ArgumentNullException(nameof(route));
if (_eventHandlers != null &&
_eventHandlers.TryGetValue(routedEvent, out var subscriptions))
{
foreach (var sub in subscriptions)
{
route.Add(this, sub.Handler, sub.Routes, sub.HandledEventsToo, sub.InvokeAdapter);
}
}
}
private readonly struct EventSubscription private readonly struct EventSubscription
{ {
public EventSubscription( public EventSubscription(

8
src/Avalonia.Base/Interactivity/InteractiveExtensions.cs

@ -5,7 +5,7 @@ using System.Reactive.Linq;
namespace Avalonia.Interactivity namespace Avalonia.Interactivity
{ {
/// <summary> /// <summary>
/// Provides extension methods for the <see cref="IInteractive"/> interface. /// Provides extension methods for the <see cref="Interactive"/> interface.
/// </summary> /// </summary>
public static class InteractiveExtensions public static class InteractiveExtensions
{ {
@ -19,7 +19,7 @@ namespace Avalonia.Interactivity
/// <param name="routes">The routing strategies to listen to.</param> /// <param name="routes">The routing strategies to listen to.</param>
/// <param name="handledEventsToo">Whether handled events should also be listened for.</param> /// <param name="handledEventsToo">Whether handled events should also be listened for.</param>
/// <returns>A disposable that terminates the event subscription.</returns> /// <returns>A disposable that terminates the event subscription.</returns>
public static IDisposable AddDisposableHandler<TEventArgs>(this IInteractive o, RoutedEvent<TEventArgs> routedEvent, public static IDisposable AddDisposableHandler<TEventArgs>(this Interactive o, RoutedEvent<TEventArgs> routedEvent,
EventHandler<TEventArgs> handler, EventHandler<TEventArgs> handler,
RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble, RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble,
bool handledEventsToo = false) where TEventArgs : RoutedEventArgs bool handledEventsToo = false) where TEventArgs : RoutedEventArgs
@ -31,6 +31,8 @@ namespace Avalonia.Interactivity
state => state.instance.RemoveHandler(state.routedEvent, state.handler)); state => state.instance.RemoveHandler(state.routedEvent, state.handler));
} }
public static Interactive? GetInteractiveParent(this Interactive o) => o.InteractiveParent;
/// <summary> /// <summary>
/// Gets an observable for a <see cref="RoutedEvent{TEventArgs}"/>. /// Gets an observable for a <see cref="RoutedEvent{TEventArgs}"/>.
/// </summary> /// </summary>
@ -42,7 +44,7 @@ namespace Avalonia.Interactivity
/// An observable which fires each time the event is raised. /// An observable which fires each time the event is raised.
/// </returns> /// </returns>
public static IObservable<TEventArgs> GetObservable<TEventArgs>( public static IObservable<TEventArgs> GetObservable<TEventArgs>(
this IInteractive o, this Interactive o,
RoutedEvent<TEventArgs> routedEvent, RoutedEvent<TEventArgs> routedEvent,
RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble, RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble,
bool handledEventsToo = false) bool handledEventsToo = false)

2
src/Avalonia.Base/Interactivity/RoutedEvent.cs

@ -116,7 +116,7 @@ namespace Avalonia.Interactivity
public IDisposable AddClassHandler<TTarget>( public IDisposable AddClassHandler<TTarget>(
Action<TTarget, TEventArgs> handler, Action<TTarget, TEventArgs> handler,
RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble, RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble,
bool handledEventsToo = false) where TTarget : class, IInteractive bool handledEventsToo = false) where TTarget : Interactive
{ {
void Adapter(object? sender, RoutedEventArgs e) void Adapter(object? sender, RoutedEventArgs e)
{ {

4
src/Avalonia.Base/Interactivity/RoutedEventArgs.cs

@ -28,7 +28,7 @@ namespace Avalonia.Interactivity
/// </summary> /// </summary>
/// <param name="routedEvent">The routed event associated with these event args.</param> /// <param name="routedEvent">The routed event associated with these event args.</param>
/// <param name="source">The source object that raised the routed event.</param> /// <param name="source">The source object that raised the routed event.</param>
public RoutedEventArgs(RoutedEvent? routedEvent, IInteractive? source) public RoutedEventArgs(RoutedEvent? routedEvent, object? source)
{ {
RoutedEvent = routedEvent; RoutedEvent = routedEvent;
Source = source; Source = source;
@ -55,6 +55,6 @@ namespace Avalonia.Interactivity
/// <summary> /// <summary>
/// Gets or sets the source object that raised the routed event. /// Gets or sets the source object that raised the routed event.
/// </summary> /// </summary>
public IInteractive? Source { get; set; } public object? Source { get; set; }
} }
} }

2
src/Avalonia.Base/Layout/AttachedLayout.cs

@ -43,7 +43,7 @@ namespace Avalonia.Layout
/// <summary> /// <summary>
/// Initializes any per-container state the layout requires when it is attached to an /// Initializes any per-container state the layout requires when it is attached to an
/// <see cref="ILayoutable"/> container. /// <see cref="Layoutable"/> container.
/// </summary> /// </summary>
/// <param name="context"> /// <param name="context">
/// The context object that facilitates communication between the layout and its host /// The context object that facilitates communication between the layout and its host

14
src/Avalonia.Base/Layout/ElementManager.cs

@ -13,7 +13,7 @@ namespace Avalonia.Layout
{ {
internal class ElementManager internal class ElementManager
{ {
private readonly List<ILayoutable?> _realizedElements = new List<ILayoutable?>(); private readonly List<Layoutable?> _realizedElements = new List<Layoutable?>();
private readonly List<Rect> _realizedElementLayoutBounds = new List<Rect>(); private readonly List<Rect> _realizedElementLayoutBounds = new List<Rect>();
private int _firstRealizedDataIndex; private int _firstRealizedDataIndex;
private VirtualizingLayoutContext? _context; private VirtualizingLayoutContext? _context;
@ -69,9 +69,9 @@ namespace Avalonia.Layout
return IsVirtualizingContext ? _realizedElements.Count : _context!.ItemCount; return IsVirtualizingContext ? _realizedElements.Count : _context!.ItemCount;
} }
public ILayoutable GetAt(int realizedIndex) public Layoutable GetAt(int realizedIndex)
{ {
ILayoutable? element; Layoutable? element;
if (IsVirtualizingContext) if (IsVirtualizingContext)
{ {
@ -99,7 +99,7 @@ namespace Avalonia.Layout
return element; return element;
} }
public void Add(ILayoutable element, int dataIndex) public void Add(Layoutable element, int dataIndex)
{ {
if (_realizedElements.Count == 0) if (_realizedElements.Count == 0)
{ {
@ -110,7 +110,7 @@ namespace Avalonia.Layout
_realizedElementLayoutBounds.Add(default); _realizedElementLayoutBounds.Add(default);
} }
public void Insert(int realizedIndex, int dataIndex, ILayoutable? element) public void Insert(int realizedIndex, int dataIndex, Layoutable? element)
{ {
if (realizedIndex == 0) if (realizedIndex == 0)
{ {
@ -207,7 +207,7 @@ namespace Avalonia.Layout
public bool IsIndexValidInData(int currentIndex) => (uint)currentIndex < _context!.ItemCount; public bool IsIndexValidInData(int currentIndex) => (uint)currentIndex < _context!.ItemCount;
public ILayoutable? GetRealizedElement(int dataIndex) public Layoutable? GetRealizedElement(int dataIndex)
{ {
return IsVirtualizingContext ? return IsVirtualizingContext ?
GetAt(GetRealizedRangeIndexFromDataIndex(dataIndex)) : GetAt(GetRealizedRangeIndexFromDataIndex(dataIndex)) :
@ -330,7 +330,7 @@ namespace Avalonia.Layout
} }
} }
public int GetElementDataIndex(ILayoutable suggestedAnchor) public int GetElementDataIndex(Layoutable suggestedAnchor)
{ {
var it = _realizedElements.IndexOf(suggestedAnchor); var it = _realizedElements.IndexOf(suggestedAnchor);
return it != -1 ? GetDataIndexFromRealizedRangeIndex(it) : -1; return it != -1 ? GetDataIndexFromRealizedRangeIndex(it) : -1;

10
src/Avalonia.Base/Layout/FlowLayoutAlgorithm.cs

@ -144,7 +144,7 @@ namespace Avalonia.Layout
} }
public Size MeasureElement( public Size MeasureElement(
ILayoutable element, Layoutable element,
int index, int index,
Size availableSize, Size availableSize,
VirtualizingLayoutContext context) VirtualizingLayoutContext context)
@ -512,9 +512,9 @@ namespace Avalonia.Layout
private Rect EstimateExtent(Size availableSize, string? layoutId) private Rect EstimateExtent(Size availableSize, string? layoutId)
{ {
ILayoutable? firstRealizedElement = null; Layoutable? firstRealizedElement = null;
Rect firstBounds = new Rect(); Rect firstBounds = new Rect();
ILayoutable? lastRealizedElement = null; Layoutable? lastRealizedElement = null;
Rect lastBounds = new Rect(); Rect lastBounds = new Rect();
int firstDataIndex = -1; int firstDataIndex = -1;
int lastDataIndex = -1; int lastDataIndex = -1;
@ -727,7 +727,7 @@ namespace Avalonia.Layout
} }
} }
public ILayoutable? GetElementIfRealized(int dataIndex) public Layoutable? GetElementIfRealized(int dataIndex)
{ {
if (_elementManager.IsDataIndexRealized(dataIndex)) if (_elementManager.IsDataIndexRealized(dataIndex))
{ {
@ -737,7 +737,7 @@ namespace Avalonia.Layout
return null; return null;
} }
public bool TryAddElement0(ILayoutable element) public bool TryAddElement0(Layoutable element)
{ {
if (_elementManager.GetRealizedElementCount() == 0) if (_elementManager.GetRealizedElementCount() == 0)
{ {

6
src/Avalonia.Base/Layout/IFlowLayoutAlgorithmDelegates.cs

@ -21,14 +21,14 @@ namespace Avalonia.Layout
Rect Algorithm_GetExtent( Rect Algorithm_GetExtent(
Size availableSize, Size availableSize,
VirtualizingLayoutContext context, VirtualizingLayoutContext context,
ILayoutable? firstRealized, Layoutable? firstRealized,
int firstRealizedItemIndex, int firstRealizedItemIndex,
Rect firstRealizedLayoutBounds, Rect firstRealizedLayoutBounds,
ILayoutable? lastRealized, Layoutable? lastRealized,
int lastRealizedItemIndex, int lastRealizedItemIndex,
Rect lastRealizedLayoutBounds); Rect lastRealizedLayoutBounds);
void Algorithm_OnElementMeasured( void Algorithm_OnElementMeasured(
ILayoutable element, Layoutable element,
int index, int index,
Size availableSize, Size availableSize,
Size measureSize, Size measureSize,

8
src/Avalonia.Base/Layout/ILayoutManager.cs

@ -18,13 +18,13 @@ namespace Avalonia.Layout
/// Notifies the layout manager that a control requires a measure. /// Notifies the layout manager that a control requires a measure.
/// </summary> /// </summary>
/// <param name="control">The control.</param> /// <param name="control">The control.</param>
void InvalidateMeasure(ILayoutable control); void InvalidateMeasure(Layoutable control);
/// <summary> /// <summary>
/// Notifies the layout manager that a control requires an arrange. /// Notifies the layout manager that a control requires an arrange.
/// </summary> /// </summary>
/// <param name="control">The control.</param> /// <param name="control">The control.</param>
void InvalidateArrange(ILayoutable control); void InvalidateArrange(Layoutable control);
/// <summary> /// <summary>
/// Executes a layout pass. /// Executes a layout pass.
@ -48,12 +48,12 @@ namespace Avalonia.Layout
/// Registers a control as wanting to receive effective viewport notifications. /// Registers a control as wanting to receive effective viewport notifications.
/// </summary> /// </summary>
/// <param name="control">The control.</param> /// <param name="control">The control.</param>
void RegisterEffectiveViewportListener(ILayoutable control); void RegisterEffectiveViewportListener(Layoutable control);
/// <summary> /// <summary>
/// Registers a control as no longer wanting to receive effective viewport notifications. /// Registers a control as no longer wanting to receive effective viewport notifications.
/// </summary> /// </summary>
/// <param name="control">The control.</param> /// <param name="control">The control.</param>
void UnregisterEffectiveViewportListener(ILayoutable control); void UnregisterEffectiveViewportListener(Layoutable control);
} }
} }

2
src/Avalonia.Base/Layout/ILayoutRoot.cs

@ -6,7 +6,7 @@ namespace Avalonia.Layout
/// Defines the root of a layoutable tree. /// Defines the root of a layoutable tree.
/// </summary> /// </summary>
[NotClientImplementable] [NotClientImplementable]
public interface ILayoutRoot : ILayoutable public interface ILayoutRoot
{ {
/// <summary> /// <summary>
/// The size available to lay out the controls. /// The size available to lay out the controls.

122
src/Avalonia.Base/Layout/ILayoutable.cs

@ -1,122 +0,0 @@
using Avalonia.Metadata;
using Avalonia.VisualTree;
namespace Avalonia.Layout
{
/// <summary>
/// Defines layout-related functionality for a control.
/// </summary>
[NotClientImplementable]
public interface ILayoutable : IVisual
{
/// <summary>
/// Gets the size that this element computed during the measure pass of the layout process.
/// </summary>
Size DesiredSize { get; }
/// <summary>
/// Gets the width of the element.
/// </summary>
double Width { get; }
/// <summary>
/// Gets the height of the element.
/// </summary>
double Height { get; }
/// <summary>
/// Gets the minimum width of the element.
/// </summary>
double MinWidth { get; }
/// <summary>
/// Gets the maximum width of the element.
/// </summary>
double MaxWidth { get; }
/// <summary>
/// Gets the minimum height of the element.
/// </summary>
double MinHeight { get; }
/// <summary>
/// Gets the maximum height of the element.
/// </summary>
double MaxHeight { get; }
/// <summary>
/// Gets the margin around the element.
/// </summary>
Thickness Margin { get; }
/// <summary>
/// Gets the element's preferred horizontal alignment in its parent.
/// </summary>
HorizontalAlignment HorizontalAlignment { get; }
/// <summary>
/// Gets the element's preferred vertical alignment in its parent.
/// </summary>
VerticalAlignment VerticalAlignment { get; }
/// <summary>
/// Gets a value indicating whether the control's layout measure is valid.
/// </summary>
bool IsMeasureValid { get; }
/// <summary>
/// Gets a value indicating whether the control's layouts arrange is valid.
/// </summary>
bool IsArrangeValid { get; }
/// <summary>
/// Gets the available size passed in the previous layout pass, if any.
/// </summary>
Size? PreviousMeasure { get; }
/// <summary>
/// Gets the layout rect passed in the previous layout pass, if any.
/// </summary>
Rect? PreviousArrange { get; }
/// <summary>
/// Creates the visual children of the control, if necessary
/// </summary>
void ApplyTemplate();
/// <summary>
/// Carries out a measure of the control.
/// </summary>
/// <param name="availableSize">The available size for the control.</param>
void Measure(Size availableSize);
/// <summary>
/// Arranges the control and its children.
/// </summary>
/// <param name="rect">The control's new bounds.</param>
void Arrange(Rect rect);
/// <summary>
/// Invalidates the measurement of the control and queues a new layout pass.
/// </summary>
void InvalidateMeasure();
/// <summary>
/// Invalidates the arrangement of the control and queues a new layout pass.
/// </summary>
void InvalidateArrange();
/// <summary>
/// Called when a child control's desired size changes.
/// </summary>
/// <param name="control">The child control.</param>
void ChildDesiredSizeChanged(ILayoutable control);
/// <summary>
/// Used by the <see cref="LayoutManager"/> to notify the control that its effective
/// viewport is changed.
/// </summary>
/// <param name="e">The viewport information.</param>
void EffectiveViewportChanged(EffectiveViewportChangedEventArgs e);
}
}

4
src/Avalonia.Base/Layout/LayoutContextAdapter.cs

@ -38,8 +38,8 @@ namespace Avalonia.Layout
protected override int ItemCountCore() => _nonVirtualizingContext.Children.Count; protected override int ItemCountCore() => _nonVirtualizingContext.Children.Count;
protected override object GetItemAtCore(int index) => _nonVirtualizingContext.Children[index]; protected override object GetItemAtCore(int index) => _nonVirtualizingContext.Children[index];
protected override ILayoutable GetOrCreateElementAtCore(int index, ElementRealizationOptions options) => protected override Layoutable GetOrCreateElementAtCore(int index, ElementRealizationOptions options) =>
_nonVirtualizingContext.Children[index]; _nonVirtualizingContext.Children[index];
protected override void RecycleElementCore(ILayoutable element) { } protected override void RecycleElementCore(Layoutable element) { }
} }
} }

43
src/Avalonia.Base/Layout/LayoutHelper.cs

@ -16,15 +16,15 @@ namespace Avalonia.Layout
public static double LayoutEpsilon { get; } = 0.00000153; public static double LayoutEpsilon { get; } = 0.00000153;
/// <summary> /// <summary>
/// Calculates a control's size based on its <see cref="ILayoutable.Width"/>, /// Calculates a control's size based on its <see cref="Layoutable.Width"/>,
/// <see cref="ILayoutable.Height"/>, <see cref="ILayoutable.MinWidth"/>, /// <see cref="Layoutable.Height"/>, <see cref="Layoutable.MinWidth"/>,
/// <see cref="ILayoutable.MaxWidth"/>, <see cref="ILayoutable.MinHeight"/> and /// <see cref="Layoutable.MaxWidth"/>, <see cref="Layoutable.MinHeight"/> and
/// <see cref="ILayoutable.MaxHeight"/>. /// <see cref="Layoutable.MaxHeight"/>.
/// </summary> /// </summary>
/// <param name="control">The control.</param> /// <param name="control">The control.</param>
/// <param name="constraints">The space available for the control.</param> /// <param name="constraints">The space available for the control.</param>
/// <returns>The control's size.</returns> /// <returns>The control's size.</returns>
public static Size ApplyLayoutConstraints(ILayoutable control, Size constraints) public static Size ApplyLayoutConstraints(Layoutable control, Size constraints)
{ {
var minmax = new MinMax(control); var minmax = new MinMax(control);
@ -33,7 +33,7 @@ namespace Avalonia.Layout
MathUtilities.Clamp(constraints.Height, minmax.MinHeight, minmax.MaxHeight)); MathUtilities.Clamp(constraints.Height, minmax.MinHeight, minmax.MaxHeight));
} }
public static Size MeasureChild(ILayoutable? control, Size availableSize, Thickness padding, public static Size MeasureChild(Layoutable? control, Size availableSize, Thickness padding,
Thickness borderThickness) Thickness borderThickness)
{ {
if (IsParentLayoutRounded(control, out double scale)) if (IsParentLayoutRounded(control, out double scale))
@ -51,7 +51,7 @@ namespace Avalonia.Layout
return new Size().Inflate(padding + borderThickness); return new Size().Inflate(padding + borderThickness);
} }
public static Size MeasureChild(ILayoutable? control, Size availableSize, Thickness padding) public static Size MeasureChild(Layoutable? control, Size availableSize, Thickness padding)
{ {
if (IsParentLayoutRounded(control, out double scale)) if (IsParentLayoutRounded(control, out double scale))
{ {
@ -67,7 +67,7 @@ namespace Avalonia.Layout
return new Size(padding.Left + padding.Right, padding.Bottom + padding.Top); return new Size(padding.Left + padding.Right, padding.Bottom + padding.Top);
} }
public static Size ArrangeChild(ILayoutable? child, Size availableSize, Thickness padding, Thickness borderThickness) public static Size ArrangeChild(Layoutable? child, Size availableSize, Thickness padding, Thickness borderThickness)
{ {
if (IsParentLayoutRounded(child, out double scale)) if (IsParentLayoutRounded(child, out double scale))
{ {
@ -78,7 +78,7 @@ namespace Avalonia.Layout
return ArrangeChildInternal(child, availableSize, padding + borderThickness); return ArrangeChildInternal(child, availableSize, padding + borderThickness);
} }
public static Size ArrangeChild(ILayoutable? child, Size availableSize, Thickness padding) public static Size ArrangeChild(Layoutable? child, Size availableSize, Thickness padding)
{ {
if(IsParentLayoutRounded(child, out double scale)) if(IsParentLayoutRounded(child, out double scale))
padding = RoundLayoutThickness(padding, scale, scale); padding = RoundLayoutThickness(padding, scale, scale);
@ -86,18 +86,18 @@ namespace Avalonia.Layout
return ArrangeChildInternal(child, availableSize, padding); return ArrangeChildInternal(child, availableSize, padding);
} }
private static Size ArrangeChildInternal(ILayoutable? child, Size availableSize, Thickness padding) private static Size ArrangeChildInternal(Layoutable? child, Size availableSize, Thickness padding)
{ {
child?.Arrange(new Rect(availableSize).Deflate(padding)); child?.Arrange(new Rect(availableSize).Deflate(padding));
return availableSize; return availableSize;
} }
private static bool IsParentLayoutRounded(ILayoutable? child, out double scale) private static bool IsParentLayoutRounded(Layoutable? child, out double scale)
{ {
var layoutableParent = (ILayoutable?)child?.GetVisualParent(); var layoutableParent = (child as Visual)?.GetVisualParent() as Layoutable;
if (layoutableParent == null || !((Layoutable)layoutableParent).UseLayoutRounding) if (layoutableParent == null || !layoutableParent.UseLayoutRounding)
{ {
scale = 1.0; scale = 1.0;
return false; return false;
@ -110,11 +110,11 @@ namespace Avalonia.Layout
/// <summary> /// <summary>
/// Invalidates measure for given control and all visual children recursively. /// Invalidates measure for given control and all visual children recursively.
/// </summary> /// </summary>
public static void InvalidateSelfAndChildrenMeasure(ILayoutable control) public static void InvalidateSelfAndChildrenMeasure(Layoutable control)
{ {
void InnerInvalidateMeasure(IVisual target) void InnerInvalidateMeasure(Visual target)
{ {
if (target is ILayoutable targetLayoutable) if (target is Layoutable targetLayoutable)
{ {
targetLayoutable.InvalidateMeasure(); targetLayoutable.InvalidateMeasure();
} }
@ -124,13 +124,14 @@ namespace Avalonia.Layout
for (int i = 0; i < visualChildrenCount; i++) for (int i = 0; i < visualChildrenCount; i++)
{ {
IVisual child = visualChildren[i]; Visual child = visualChildren[i];
InnerInvalidateMeasure(child); InnerInvalidateMeasure(child);
} }
} }
InnerInvalidateMeasure(control); if (control is Visual v)
InnerInvalidateMeasure(v);
} }
/// <summary> /// <summary>
@ -138,9 +139,9 @@ namespace Avalonia.Layout
/// </summary> /// </summary>
/// <param name="control">The control.</param> /// <param name="control">The control.</param>
/// <exception cref="Exception">Thrown when control has no root or returned layout scaling is invalid.</exception> /// <exception cref="Exception">Thrown when control has no root or returned layout scaling is invalid.</exception>
public static double GetLayoutScale(ILayoutable control) public static double GetLayoutScale(Layoutable control)
{ {
var visualRoot = control.VisualRoot; var visualRoot = (control as Visual)?.VisualRoot;
var result = (visualRoot as ILayoutRoot)?.LayoutScaling ?? 1.0; var result = (visualRoot as ILayoutRoot)?.LayoutScaling ?? 1.0;
@ -289,7 +290,7 @@ namespace Avalonia.Layout
/// </summary> /// </summary>
private readonly struct MinMax private readonly struct MinMax
{ {
public MinMax(ILayoutable e) public MinMax(Layoutable e)
{ {
MaxHeight = e.MaxHeight; MaxHeight = e.MaxHeight;
MinHeight = e.MinHeight; MinHeight = e.MinHeight;

38
src/Avalonia.Base/Layout/LayoutManager.cs

@ -16,9 +16,9 @@ namespace Avalonia.Layout
public class LayoutManager : ILayoutManager, IDisposable public class LayoutManager : ILayoutManager, IDisposable
{ {
private const int MaxPasses = 3; private const int MaxPasses = 3;
private readonly ILayoutRoot _owner; private readonly Layoutable _owner;
private readonly LayoutQueue<ILayoutable> _toMeasure = new LayoutQueue<ILayoutable>(v => !v.IsMeasureValid); private readonly LayoutQueue<Layoutable> _toMeasure = new LayoutQueue<Layoutable>(v => !v.IsMeasureValid);
private readonly LayoutQueue<ILayoutable> _toArrange = new LayoutQueue<ILayoutable>(v => !v.IsArrangeValid); private readonly LayoutQueue<Layoutable> _toArrange = new LayoutQueue<Layoutable>(v => !v.IsArrangeValid);
private readonly Action _executeLayoutPass; private readonly Action _executeLayoutPass;
private List<EffectiveViewportChangedListener>? _effectiveViewportChangedListeners; private List<EffectiveViewportChangedListener>? _effectiveViewportChangedListeners;
private bool _disposed; private bool _disposed;
@ -27,14 +27,14 @@ namespace Avalonia.Layout
public LayoutManager(ILayoutRoot owner) public LayoutManager(ILayoutRoot owner)
{ {
_owner = owner ?? throw new ArgumentNullException(nameof(owner)); _owner = owner as Layoutable ?? throw new ArgumentNullException(nameof(owner));
_executeLayoutPass = ExecuteQueuedLayoutPass; _executeLayoutPass = ExecuteQueuedLayoutPass;
} }
public virtual event EventHandler? LayoutUpdated; public virtual event EventHandler? LayoutUpdated;
/// <inheritdoc/> /// <inheritdoc/>
public virtual void InvalidateMeasure(ILayoutable control) public virtual void InvalidateMeasure(Layoutable control)
{ {
control = control ?? throw new ArgumentNullException(nameof(control)); control = control ?? throw new ArgumentNullException(nameof(control));
Dispatcher.UIThread.VerifyAccess(); Dispatcher.UIThread.VerifyAccess();
@ -65,7 +65,7 @@ namespace Avalonia.Layout
} }
/// <inheritdoc/> /// <inheritdoc/>
public virtual void InvalidateArrange(ILayoutable control) public virtual void InvalidateArrange(Layoutable control)
{ {
control = control ?? throw new ArgumentNullException(nameof(control)); control = control ?? throw new ArgumentNullException(nameof(control));
Dispatcher.UIThread.VerifyAccess(); Dispatcher.UIThread.VerifyAccess();
@ -203,7 +203,7 @@ namespace Avalonia.Layout
_toArrange.Dispose(); _toArrange.Dispose();
} }
void ILayoutManager.RegisterEffectiveViewportListener(ILayoutable control) void ILayoutManager.RegisterEffectiveViewportListener(Layoutable control)
{ {
_effectiveViewportChangedListeners ??= new List<EffectiveViewportChangedListener>(); _effectiveViewportChangedListeners ??= new List<EffectiveViewportChangedListener>();
_effectiveViewportChangedListeners.Add(new EffectiveViewportChangedListener( _effectiveViewportChangedListeners.Add(new EffectiveViewportChangedListener(
@ -211,7 +211,7 @@ namespace Avalonia.Layout
CalculateEffectiveViewport(control))); CalculateEffectiveViewport(control)));
} }
void ILayoutManager.UnregisterEffectiveViewportListener(ILayoutable control) void ILayoutManager.UnregisterEffectiveViewportListener(Layoutable control)
{ {
if (_effectiveViewportChangedListeners is object) if (_effectiveViewportChangedListeners is object)
{ {
@ -265,13 +265,13 @@ namespace Avalonia.Layout
} }
} }
private void Measure(ILayoutable control) private void Measure(Layoutable control)
{ {
// Controls closest to the visual root need to be arranged first. We don't try to store // Controls closest to the visual root need to be arranged first. We don't try to store
// ordered invalidation lists, instead we traverse the tree upwards, measuring the // ordered invalidation lists, instead we traverse the tree upwards, measuring the
// controls closest to the root first. This has been shown by benchmarks to be the // controls closest to the root first. This has been shown by benchmarks to be the
// fastest and most memory-efficient algorithm. // fastest and most memory-efficient algorithm.
if (control.VisualParent is ILayoutable parent) if (control.VisualParent is Layoutable parent)
{ {
Measure(parent); Measure(parent);
} }
@ -283,7 +283,7 @@ namespace Avalonia.Layout
{ {
if (control is ILayoutRoot root) if (control is ILayoutRoot root)
{ {
root.Measure(Size.Infinity); control.Measure(Size.Infinity);
} }
else if (control.PreviousMeasure.HasValue) else if (control.PreviousMeasure.HasValue)
{ {
@ -292,9 +292,9 @@ namespace Avalonia.Layout
} }
} }
private void Arrange(ILayoutable control) private void Arrange(Layoutable control)
{ {
if (control.VisualParent is ILayoutable parent) if (control.VisualParent is Layoutable parent)
{ {
Arrange(parent); Arrange(parent);
} }
@ -304,7 +304,7 @@ namespace Avalonia.Layout
if (control is IEmbeddedLayoutRoot embeddedRoot) if (control is IEmbeddedLayoutRoot embeddedRoot)
control.Arrange(new Rect(embeddedRoot.AllocatedSize)); control.Arrange(new Rect(embeddedRoot.AllocatedSize));
else if (control is ILayoutRoot root) else if (control is ILayoutRoot root)
control.Arrange(new Rect(root.DesiredSize)); control.Arrange(new Rect(control.DesiredSize));
else if (control.PreviousArrange != null) else if (control.PreviousArrange != null)
{ {
// Has been observed that PreviousArrange sometimes is null, probably a bug somewhere else. // Has been observed that PreviousArrange sometimes is null, probably a bug somewhere else.
@ -350,7 +350,7 @@ namespace Avalonia.Layout
if (viewport != l.Viewport) if (viewport != l.Viewport)
{ {
l.Listener.EffectiveViewportChanged(new EffectiveViewportChangedEventArgs(viewport)); l.Listener.RaiseEffectiveViewportChanged(new EffectiveViewportChangedEventArgs(viewport));
l.Viewport = viewport; l.Viewport = viewport;
} }
} }
@ -364,14 +364,14 @@ namespace Avalonia.Layout
return startCount != _toMeasure.Count + _toArrange.Count; return startCount != _toMeasure.Count + _toArrange.Count;
} }
private Rect CalculateEffectiveViewport(IVisual control) private Rect CalculateEffectiveViewport(Visual control)
{ {
var viewport = new Rect(0, 0, double.PositiveInfinity, double.PositiveInfinity); var viewport = new Rect(0, 0, double.PositiveInfinity, double.PositiveInfinity);
CalculateEffectiveViewport(control, control, ref viewport); CalculateEffectiveViewport(control, control, ref viewport);
return viewport; return viewport;
} }
private void CalculateEffectiveViewport(IVisual target, IVisual control, ref Rect viewport) private void CalculateEffectiveViewport(Visual target, Visual control, ref Rect viewport)
{ {
// Recurse until the top level control. // Recurse until the top level control.
if (control.VisualParent is object) if (control.VisualParent is object)
@ -405,13 +405,13 @@ namespace Avalonia.Layout
private class EffectiveViewportChangedListener private class EffectiveViewportChangedListener
{ {
public EffectiveViewportChangedListener(ILayoutable listener, Rect viewport) public EffectiveViewportChangedListener(Layoutable listener, Rect viewport)
{ {
Listener = listener; Listener = listener;
Viewport = viewport; Viewport = viewport;
} }
public ILayoutable Listener { get; } public Layoutable Listener { get; }
public Rect Viewport { get; set; } public Rect Viewport { get; set; }
} }
} }

35
src/Avalonia.Base/Layout/Layoutable.cs

@ -62,7 +62,7 @@ namespace Avalonia.Layout
/// <summary> /// <summary>
/// Implements layout-related functionality for a control. /// Implements layout-related functionality for a control.
/// </summary> /// </summary>
public class Layoutable : Visual, ILayoutable public class Layoutable : Visual
{ {
/// <summary> /// <summary>
/// Defines the <see cref="DesiredSize"/> property. /// Defines the <see cref="DesiredSize"/> property.
@ -323,15 +323,12 @@ namespace Avalonia.Layout
set { SetValue(UseLayoutRoundingProperty, value); } set { SetValue(UseLayoutRoundingProperty, value); }
} }
/// <summary> internal Size? PreviousMeasure => _previousMeasure;
/// Gets the available size passed in the previous layout pass, if any.
/// </summary>
Size? ILayoutable.PreviousMeasure => _previousMeasure;
/// <summary> /// <summary>
/// Gets the layout rect passed in the previous layout pass, if any. /// Gets the layout rect passed in the previous layout pass, if any.
/// </summary> /// </summary>
Rect? ILayoutable.PreviousArrange => _previousArrange; internal Rect? PreviousArrange => _previousArrange;
/// <summary> /// <summary>
/// Creates the visual children of the control, if necessary /// Creates the visual children of the control, if necessary
@ -380,7 +377,7 @@ namespace Avalonia.Layout
if (DesiredSize != previousDesiredSize) if (DesiredSize != previousDesiredSize)
{ {
this.GetVisualParent<ILayoutable>()?.ChildDesiredSizeChanged(this); this.GetVisualParent<Layoutable>()?.ChildDesiredSizeChanged(this);
} }
} }
} }
@ -423,7 +420,7 @@ namespace Avalonia.Layout
IsMeasureValid = false; IsMeasureValid = false;
IsArrangeValid = false; IsArrangeValid = false;
if (((ILayoutable)this).IsAttachedToVisualTree) if (IsAttachedToVisualTree)
{ {
(VisualRoot as ILayoutRoot)?.LayoutManager.InvalidateMeasure(this); (VisualRoot as ILayoutRoot)?.LayoutManager.InvalidateMeasure(this);
InvalidateVisual(); InvalidateVisual();
@ -448,7 +445,7 @@ namespace Avalonia.Layout
} }
/// <inheritdoc/> /// <inheritdoc/>
void ILayoutable.ChildDesiredSizeChanged(ILayoutable control) internal void ChildDesiredSizeChanged(Layoutable control)
{ {
if (!_measuring) if (!_measuring)
{ {
@ -456,11 +453,11 @@ namespace Avalonia.Layout
} }
} }
void ILayoutable.EffectiveViewportChanged(EffectiveViewportChangedEventArgs e) internal void RaiseEffectiveViewportChanged(EffectiveViewportChangedEventArgs e)
{ {
_effectiveViewportChanged?.Invoke(this, e); _effectiveViewportChanged?.Invoke(this, e);
} }
/// <summary> /// <summary>
/// Marks a property as affecting the control's measurement. /// Marks a property as affecting the control's measurement.
/// </summary> /// </summary>
@ -471,7 +468,7 @@ namespace Avalonia.Layout
/// property will cause <see cref="InvalidateMeasure"/> to be called on the element. /// property will cause <see cref="InvalidateMeasure"/> to be called on the element.
/// </remarks> /// </remarks>
protected static void AffectsMeasure<T>(params AvaloniaProperty[] properties) protected static void AffectsMeasure<T>(params AvaloniaProperty[] properties)
where T : class, ILayoutable where T : Layoutable
{ {
void Invalidate(AvaloniaPropertyChangedEventArgs e) void Invalidate(AvaloniaPropertyChangedEventArgs e)
{ {
@ -494,7 +491,7 @@ namespace Avalonia.Layout
/// property will cause <see cref="InvalidateArrange"/> to be called on the element. /// property will cause <see cref="InvalidateArrange"/> to be called on the element.
/// </remarks> /// </remarks>
protected static void AffectsArrange<T>(params AvaloniaProperty[] properties) protected static void AffectsArrange<T>(params AvaloniaProperty[] properties)
where T : class, ILayoutable where T : Layoutable
{ {
void Invalidate(AvaloniaPropertyChangedEventArgs e) void Invalidate(AvaloniaPropertyChangedEventArgs e)
{ {
@ -596,9 +593,9 @@ namespace Avalonia.Layout
for (var i = 0; i < visualCount; i++) for (var i = 0; i < visualCount; i++)
{ {
IVisual visual = visualChildren[i]; Visual visual = visualChildren[i];
if (visual is ILayoutable layoutable) if (visual is Layoutable layoutable)
{ {
layoutable.Measure(availableSize); layoutable.Measure(availableSize);
width = Math.Max(width, layoutable.DesiredSize.Width); width = Math.Max(width, layoutable.DesiredSize.Width);
@ -709,9 +706,9 @@ namespace Avalonia.Layout
for (var i = 0; i < visualCount; i++) for (var i = 0; i < visualCount; i++)
{ {
IVisual visual = visualChildren[i]; Visual visual = visualChildren[i];
if (visual is ILayoutable layoutable) if (visual is Layoutable layoutable)
{ {
layoutable.Arrange(arrangeRect); layoutable.Arrange(arrangeRect);
} }
@ -778,7 +775,7 @@ namespace Avalonia.Layout
DesiredSize = default; DesiredSize = default;
// All changes to visibility cause the parent element to be notified. // All changes to visibility cause the parent element to be notified.
this.GetVisualParent<ILayoutable>()?.ChildDesiredSizeChanged(this); this.GetVisualParent<Layoutable>()?.ChildDesiredSizeChanged(this);
// We only invalidate outselves when visibility is changed to true. // We only invalidate outselves when visibility is changed to true.
if (change.GetNewValue<bool>()) if (change.GetNewValue<bool>())
@ -789,7 +786,7 @@ namespace Avalonia.Layout
} }
/// <inheritdoc/> /// <inheritdoc/>
protected sealed override void OnVisualParentChanged(IVisual? oldParent, IVisual? newParent) protected sealed override void OnVisualParentChanged(Visual? oldParent, Visual? newParent)
{ {
LayoutHelper.InvalidateSelfAndChildrenMeasure(this); LayoutHelper.InvalidateSelfAndChildrenMeasure(this);

4
src/Avalonia.Base/Layout/NonVirtualizingLayoutContext.cs

@ -17,13 +17,13 @@ namespace Avalonia.Layout
/// <summary> /// <summary>
/// Gets the collection of child controls from the container that provides the context. /// Gets the collection of child controls from the container that provides the context.
/// </summary> /// </summary>
public IReadOnlyList<ILayoutable> Children => ChildrenCore; public IReadOnlyList<Layoutable> Children => ChildrenCore;
/// <summary> /// <summary>
/// Implements the behavior for getting the return value of <see cref="Children"/> in a /// Implements the behavior for getting the return value of <see cref="Children"/> in a
/// derived or custom <see cref="NonVirtualizingLayoutContext"/>. /// derived or custom <see cref="NonVirtualizingLayoutContext"/>.
/// </summary> /// </summary>
protected abstract IReadOnlyList<ILayoutable> ChildrenCore { get; } protected abstract IReadOnlyList<Layoutable> ChildrenCore { get; }
internal VirtualizingLayoutContext GetVirtualizingContextAdapter() => internal VirtualizingLayoutContext GetVirtualizingContextAdapter() =>
_contextAdapter ??= new LayoutContextAdapter(this); _contextAdapter ??= new LayoutContextAdapter(this);

8
src/Avalonia.Base/Layout/NonVirtualizingStackLayout.cs

@ -59,7 +59,7 @@ namespace Avalonia.Layout
{ {
var element = context.Children[i]; var element = context.Children[i];
if (!element.IsVisible) if ((element as Visual)?.IsVisible == false)
{ {
continue; continue;
} }
@ -100,7 +100,7 @@ namespace Avalonia.Layout
{ {
var element = context.Children[i]; var element = context.Children[i];
if (!element.IsVisible) if ((element as Visual)?.IsVisible == false)
{ {
continue; continue;
} }
@ -117,7 +117,7 @@ namespace Avalonia.Layout
Math.Max(finalSize.Height, bounds.Height)); Math.Max(finalSize.Height, bounds.Height));
} }
private static Rect LayoutVertical(ILayoutable element, double y, Size constraint) private static Rect LayoutVertical(Layoutable element, double y, Size constraint)
{ {
var x = 0.0; var x = 0.0;
var width = element.DesiredSize.Width; var width = element.DesiredSize.Width;
@ -138,7 +138,7 @@ namespace Avalonia.Layout
return new Rect(x, y, width, element.DesiredSize.Height); return new Rect(x, y, width, element.DesiredSize.Height);
} }
private static Rect LayoutHorizontal(ILayoutable element, double x, Size constraint) private static Rect LayoutHorizontal(Layoutable element, double x, Size constraint)
{ {
var y = 0.0; var y = 0.0;
var height = element.DesiredSize.Height; var height = element.DesiredSize.Height;

12
src/Avalonia.Base/Layout/StackLayout.cs

@ -78,10 +78,10 @@ namespace Avalonia.Layout
internal Rect GetExtent( internal Rect GetExtent(
Size availableSize, Size availableSize,
VirtualizingLayoutContext context, VirtualizingLayoutContext context,
ILayoutable? firstRealized, Layoutable? firstRealized,
int firstRealizedItemIndex, int firstRealizedItemIndex,
Rect firstRealizedLayoutBounds, Rect firstRealizedLayoutBounds,
ILayoutable? lastRealized, Layoutable? lastRealized,
int lastRealizedItemIndex, int lastRealizedItemIndex,
Rect lastRealizedLayoutBounds) Rect lastRealizedLayoutBounds)
{ {
@ -121,7 +121,7 @@ namespace Avalonia.Layout
} }
internal void OnElementMeasured( internal void OnElementMeasured(
ILayoutable element, Layoutable element,
int index, int index,
Size availableSize, Size availableSize,
Size measureSize, Size measureSize,
@ -188,10 +188,10 @@ namespace Avalonia.Layout
Rect IFlowLayoutAlgorithmDelegates.Algorithm_GetExtent( Rect IFlowLayoutAlgorithmDelegates.Algorithm_GetExtent(
Size availableSize, Size availableSize,
VirtualizingLayoutContext context, VirtualizingLayoutContext context,
ILayoutable? firstRealized, Layoutable? firstRealized,
int firstRealizedItemIndex, int firstRealizedItemIndex,
Rect firstRealizedLayoutBounds, Rect firstRealizedLayoutBounds,
ILayoutable? lastRealized, Layoutable? lastRealized,
int lastRealizedItemIndex, int lastRealizedItemIndex,
Rect lastRealizedLayoutBounds) Rect lastRealizedLayoutBounds)
{ {
@ -206,7 +206,7 @@ namespace Avalonia.Layout
lastRealizedLayoutBounds); lastRealizedLayoutBounds);
} }
void IFlowLayoutAlgorithmDelegates.Algorithm_OnElementMeasured(ILayoutable element, int index, Size availableSize, Size measureSize, Size desiredSize, Size provisionalArrangeSize, VirtualizingLayoutContext context) void IFlowLayoutAlgorithmDelegates.Algorithm_OnElementMeasured(Layoutable element, int index, Size availableSize, Size measureSize, Size desiredSize, Size provisionalArrangeSize, VirtualizingLayoutContext context)
{ {
OnElementMeasured( OnElementMeasured(
element, element,

6
src/Avalonia.Base/Layout/UniformGridLayout.cs

@ -338,10 +338,10 @@ namespace Avalonia.Layout
Rect IFlowLayoutAlgorithmDelegates.Algorithm_GetExtent( Rect IFlowLayoutAlgorithmDelegates.Algorithm_GetExtent(
Size availableSize, Size availableSize,
VirtualizingLayoutContext context, VirtualizingLayoutContext context,
ILayoutable? firstRealized, Layoutable? firstRealized,
int firstRealizedItemIndex, int firstRealizedItemIndex,
Rect firstRealizedLayoutBounds, Rect firstRealizedLayoutBounds,
ILayoutable? lastRealized, Layoutable? lastRealized,
int lastRealizedItemIndex, int lastRealizedItemIndex,
Rect lastRealizedLayoutBounds) Rect lastRealizedLayoutBounds)
{ {
@ -391,7 +391,7 @@ namespace Avalonia.Layout
return extent; return extent;
} }
void IFlowLayoutAlgorithmDelegates.Algorithm_OnElementMeasured(ILayoutable element, int index, Size availableSize, Size measureSize, Size desiredSize, Size provisionalArrangeSize, VirtualizingLayoutContext context) void IFlowLayoutAlgorithmDelegates.Algorithm_OnElementMeasured(Layoutable element, int index, Size availableSize, Size measureSize, Size desiredSize, Size provisionalArrangeSize, VirtualizingLayoutContext context)
{ {
} }

4
src/Avalonia.Base/Layout/UniformGridLayoutState.cs

@ -18,7 +18,7 @@ namespace Avalonia.Layout
// If it does not, then we need to do context.GetElement(0) at which point we have requested an element and are on point to clear it. // If it does not, then we need to do context.GetElement(0) at which point we have requested an element and are on point to clear it.
// If we are responsible for clearing element 0 we keep m_cachedFirstElement valid. // If we are responsible for clearing element 0 we keep m_cachedFirstElement valid.
// If we are not (because FlowLayoutAlgorithm is holding it for us) then we just null out this field and use the one from FlowLayoutAlgorithm. // If we are not (because FlowLayoutAlgorithm is holding it for us) then we just null out this field and use the one from FlowLayoutAlgorithm.
private ILayoutable? _cachedFirstElement; private Layoutable? _cachedFirstElement;
internal FlowLayoutAlgorithm FlowAlgorithm { get; } = new FlowLayoutAlgorithm(); internal FlowLayoutAlgorithm FlowAlgorithm { get; } = new FlowLayoutAlgorithm();
internal double EffectiveItemWidth { get; private set; } internal double EffectiveItemWidth { get; private set; }
@ -91,7 +91,7 @@ namespace Avalonia.Layout
} }
private void SetSize( private void SetSize(
ILayoutable element, Layoutable element,
double layoutItemWidth, double layoutItemWidth,
double layoutItemHeight, double layoutItemHeight,
Size availableSize, Size availableSize,

8
src/Avalonia.Base/Layout/VirtualLayoutContextAdapter.cs

@ -19,18 +19,18 @@ namespace Avalonia.Layout
set => _virtualizingContext.LayoutState = value; set => _virtualizingContext.LayoutState = value;
} }
protected override IReadOnlyList<ILayoutable> ChildrenCore => protected override IReadOnlyList<Layoutable> ChildrenCore =>
_children ?? (_children = new ChildrenCollection(_virtualizingContext)); _children ?? (_children = new ChildrenCollection(_virtualizingContext));
private class ChildrenCollection : IReadOnlyList<ILayoutable> private class ChildrenCollection : IReadOnlyList<Layoutable>
{ {
private readonly VirtualizingLayoutContext _context; private readonly VirtualizingLayoutContext _context;
public ChildrenCollection(VirtualizingLayoutContext context) => _context = context; public ChildrenCollection(VirtualizingLayoutContext context) => _context = context;
public ILayoutable this[int index] => _context.GetOrCreateElementAt(index); public Layoutable this[int index] => _context.GetOrCreateElementAt(index);
public int Count => _context.ItemCount; public int Count => _context.ItemCount;
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public IEnumerator<ILayoutable> GetEnumerator() public IEnumerator<Layoutable> GetEnumerator()
{ {
for (var i = 0; i < Count; ++i) for (var i = 0; i < Count; ++i)
{ {

12
src/Avalonia.Base/Layout/VirtualizingLayoutContext.cs

@ -116,7 +116,7 @@ namespace Avalonia.Layout
/// This method calls <see cref="GetOrCreateElementAtCore(int, ElementRealizationOptions)"/> /// This method calls <see cref="GetOrCreateElementAtCore(int, ElementRealizationOptions)"/>
/// with options set to None. GetElementAtCore must be implemented in a derived class. /// with options set to None. GetElementAtCore must be implemented in a derived class.
/// </remarks> /// </remarks>
public ILayoutable GetOrCreateElementAt(int index) public Layoutable GetOrCreateElementAt(int index)
=> GetOrCreateElementAtCore(index, ElementRealizationOptions.None); => GetOrCreateElementAtCore(index, ElementRealizationOptions.None);
/// <summary> /// <summary>
@ -140,7 +140,7 @@ namespace Avalonia.Layout
/// advanced layouts that choose to explicitly manage the realization and recycling of /// advanced layouts that choose to explicitly manage the realization and recycling of
/// elements as a performance optimization. /// elements as a performance optimization.
/// </remarks> /// </remarks>
public ILayoutable GetOrCreateElementAt(int index, ElementRealizationOptions options) public Layoutable GetOrCreateElementAt(int index, ElementRealizationOptions options)
=> GetOrCreateElementAtCore(index, options); => GetOrCreateElementAtCore(index, options);
/// <summary> /// <summary>
@ -148,10 +148,10 @@ namespace Avalonia.Layout
/// </summary> /// </summary>
/// <param name="element">The element to clear.</param> /// <param name="element">The element to clear.</param>
/// <remarks> /// <remarks>
/// This method calls <see cref="RecycleElementCore(ILayoutable)"/>, which must be implemented /// This method calls <see cref="RecycleElementCore(Layoutable)"/>, which must be implemented
/// in a derived class. /// in a derived class.
/// </remarks> /// </remarks>
public void RecycleElement(ILayoutable element) => RecycleElementCore(element); public void RecycleElement(Layoutable element) => RecycleElementCore(element);
/// <summary> /// <summary>
/// When implemented in a derived class, retrieves the number of items in the data. /// When implemented in a derived class, retrieves the number of items in the data.
@ -180,14 +180,14 @@ namespace Avalonia.Layout
/// A value of <see cref="ElementRealizationOptions"/> that specifies whether to suppress /// A value of <see cref="ElementRealizationOptions"/> that specifies whether to suppress
/// automatic recycling of the retrieved element or force creation of a new element. /// automatic recycling of the retrieved element or force creation of a new element.
/// </param> /// </param>
protected abstract ILayoutable GetOrCreateElementAtCore(int index, ElementRealizationOptions options); protected abstract Layoutable GetOrCreateElementAtCore(int index, ElementRealizationOptions options);
/// <summary> /// <summary>
/// When implemented in a derived class, clears the specified UIElement and allows it to be /// When implemented in a derived class, clears the specified UIElement and allows it to be
/// either re-used or released. /// either re-used or released.
/// </summary> /// </summary>
/// <param name="element">The element to clear.</param> /// <param name="element">The element to clear.</param>
protected abstract void RecycleElementCore(ILayoutable element); protected abstract void RecycleElementCore(Layoutable element);
internal NonVirtualizingLayoutContext GetNonVirtualizingContextAdapter() => internal NonVirtualizingLayoutContext GetNonVirtualizingContextAdapter() =>
_contextAdapter ?? (_contextAdapter = new VirtualLayoutContextAdapter(this)); _contextAdapter ?? (_contextAdapter = new VirtualLayoutContextAdapter(this));

2
src/Avalonia.Base/Layout/WrapLayout/WrapItem.cs

@ -20,6 +20,6 @@ namespace Avalonia.Layout
public UvMeasure? Position { get; internal set; } public UvMeasure? Position { get; internal set; }
public ILayoutable? Element { get; internal set; } public Layoutable? Element { get; internal set; }
} }
} }

4
src/Avalonia.Base/Media/IVisualBrush.cs

@ -4,7 +4,7 @@ using Avalonia.VisualTree;
namespace Avalonia.Media namespace Avalonia.Media
{ {
/// <summary> /// <summary>
/// Paints an area with an <see cref="IVisual"/>. /// Paints an area with an <see cref="Visual"/>.
/// </summary> /// </summary>
[NotClientImplementable] [NotClientImplementable]
public interface IVisualBrush : ITileBrush public interface IVisualBrush : ITileBrush
@ -12,6 +12,6 @@ namespace Avalonia.Media
/// <summary> /// <summary>
/// Gets the visual to draw. /// Gets the visual to draw.
/// </summary> /// </summary>
IVisual Visual { get; } Visual Visual { get; }
} }
} }

2
src/Avalonia.Base/Media/Imaging/RenderTargetBitmap.cs

@ -44,7 +44,7 @@ namespace Avalonia.Media.Imaging
/// Renders a visual to the <see cref="RenderTargetBitmap"/>. /// Renders a visual to the <see cref="RenderTargetBitmap"/>.
/// </summary> /// </summary>
/// <param name="visual">The visual to render.</param> /// <param name="visual">The visual to render.</param>
public void Render(IVisual visual) => ImmediateRenderer.Render(visual, this); public void Render(Visual visual) => ImmediateRenderer.Render(visual, this);
/// <summary> /// <summary>
/// Creates a platform-specific implementation for a <see cref="RenderTargetBitmap"/>. /// Creates a platform-specific implementation for a <see cref="RenderTargetBitmap"/>.

4
src/Avalonia.Base/Media/Immutable/ImmutableVisualBrush.cs

@ -25,7 +25,7 @@ namespace Avalonia.Media.Immutable
/// <param name="tileMode">The tile mode.</param> /// <param name="tileMode">The tile mode.</param>
/// <param name="bitmapInterpolationMode">Controls the quality of interpolation.</param> /// <param name="bitmapInterpolationMode">Controls the quality of interpolation.</param>
public ImmutableVisualBrush( public ImmutableVisualBrush(
IVisual visual, Visual visual,
AlignmentX alignmentX = AlignmentX.Center, AlignmentX alignmentX = AlignmentX.Center,
AlignmentY alignmentY = AlignmentY.Center, AlignmentY alignmentY = AlignmentY.Center,
RelativeRect? destinationRect = null, RelativeRect? destinationRect = null,
@ -62,6 +62,6 @@ namespace Avalonia.Media.Immutable
} }
/// <inheritdoc/> /// <inheritdoc/>
public IVisual Visual { get; } public Visual Visual { get; }
} }
} }

10
src/Avalonia.Base/Media/VisualBrush.cs

@ -4,15 +4,15 @@ using Avalonia.VisualTree;
namespace Avalonia.Media namespace Avalonia.Media
{ {
/// <summary> /// <summary>
/// Paints an area with an <see cref="IVisual"/>. /// Paints an area with an <see cref="Visual"/>.
/// </summary> /// </summary>
public class VisualBrush : TileBrush, IVisualBrush public class VisualBrush : TileBrush, IVisualBrush
{ {
/// <summary> /// <summary>
/// Defines the <see cref="Visual"/> property. /// Defines the <see cref="Visual"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<IVisual> VisualProperty = public static readonly StyledProperty<Visual> VisualProperty =
AvaloniaProperty.Register<VisualBrush, IVisual>(nameof(Visual)); AvaloniaProperty.Register<VisualBrush, Visual>(nameof(Visual));
static VisualBrush() static VisualBrush()
{ {
@ -30,7 +30,7 @@ namespace Avalonia.Media
/// Initializes a new instance of the <see cref="VisualBrush"/> class. /// Initializes a new instance of the <see cref="VisualBrush"/> class.
/// </summary> /// </summary>
/// <param name="visual">The visual to draw.</param> /// <param name="visual">The visual to draw.</param>
public VisualBrush(IVisual visual) public VisualBrush(Visual visual)
{ {
Visual = visual; Visual = visual;
} }
@ -38,7 +38,7 @@ namespace Avalonia.Media
/// <summary> /// <summary>
/// Gets or sets the visual to draw. /// Gets or sets the visual to draw.
/// </summary> /// </summary>
public IVisual Visual public Visual Visual
{ {
get { return GetValue(VisualProperty); } get { return GetValue(VisualProperty); }
set { SetValue(VisualProperty, value); } set { SetValue(VisualProperty, value); }

2
src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs

@ -262,7 +262,7 @@ namespace Avalonia.PropertyStore
private class UncommonFields private class UncommonFields
{ {
public Func<IAvaloniaObject, T, T>? _coerce; public Func<AvaloniaObject, T, T>? _coerce;
public T? _uncoercedValue; public T? _uncoercedValue;
public T? _uncoercedBaseValue; public T? _uncoercedBaseValue;
} }

12
src/Avalonia.Base/PropertyStore/UntypedValueUtils.cs

@ -23,18 +23,6 @@ namespace Avalonia.PropertyStore
return v; return v;
} }
public static bool TryConvertAndValidate(
AvaloniaProperty property,
object? value,
out object? result)
{
if (TypeUtilities.TryConvertImplicit(property.PropertyType, value, out result))
return ((IStyledPropertyAccessor)property).ValidateValue(result);
result = default;
return false;
}
public static bool TryConvertAndValidate<T>( public static bool TryConvertAndValidate<T>(
StyledPropertyBase<T> property, StyledPropertyBase<T> property,
object? value, object? value,

6
src/Avalonia.Base/Reactive/AvaloniaPropertyBindingObservable.cs

@ -6,15 +6,15 @@ namespace Avalonia.Reactive
{ {
internal class AvaloniaPropertyBindingObservable<T> : LightweightObservableBase<BindingValue<T>>, IDescription internal class AvaloniaPropertyBindingObservable<T> : LightweightObservableBase<BindingValue<T>>, IDescription
{ {
private readonly WeakReference<IAvaloniaObject> _target; private readonly WeakReference<AvaloniaObject> _target;
private readonly AvaloniaProperty _property; private readonly AvaloniaProperty _property;
private BindingValue<T> _value = BindingValue<T>.Unset; private BindingValue<T> _value = BindingValue<T>.Unset;
public AvaloniaPropertyBindingObservable( public AvaloniaPropertyBindingObservable(
IAvaloniaObject target, AvaloniaObject target,
AvaloniaProperty property) AvaloniaProperty property)
{ {
_target = new WeakReference<IAvaloniaObject>(target); _target = new WeakReference<AvaloniaObject>(target);
_property = property; _property = property;
} }

6
src/Avalonia.Base/Reactive/AvaloniaPropertyChangedObservable.cs

@ -6,14 +6,14 @@ namespace Avalonia.Reactive
LightweightObservableBase<AvaloniaPropertyChangedEventArgs>, LightweightObservableBase<AvaloniaPropertyChangedEventArgs>,
IDescription IDescription
{ {
private readonly WeakReference<IAvaloniaObject> _target; private readonly WeakReference<AvaloniaObject> _target;
private readonly AvaloniaProperty _property; private readonly AvaloniaProperty _property;
public AvaloniaPropertyChangedObservable( public AvaloniaPropertyChangedObservable(
IAvaloniaObject target, AvaloniaObject target,
AvaloniaProperty property) AvaloniaProperty property)
{ {
_target = new WeakReference<IAvaloniaObject>(target); _target = new WeakReference<AvaloniaObject>(target);
_property = property; _property = property;
} }

36
src/Avalonia.Base/Reactive/AvaloniaPropertyObservable.cs

@ -6,15 +6,15 @@ namespace Avalonia.Reactive
{ {
internal class AvaloniaPropertyObservable<T> : LightweightObservableBase<T>, IDescription internal class AvaloniaPropertyObservable<T> : LightweightObservableBase<T>, IDescription
{ {
private readonly WeakReference<IAvaloniaObject> _target; private readonly WeakReference<AvaloniaObject> _target;
private readonly AvaloniaProperty _property; private readonly AvaloniaProperty _property;
private Optional<T> _value; private Optional<T> _value;
public AvaloniaPropertyObservable( public AvaloniaPropertyObservable(
IAvaloniaObject target, AvaloniaObject target,
AvaloniaProperty property) AvaloniaProperty property)
{ {
_target = new WeakReference<IAvaloniaObject>(target); _target = new WeakReference<AvaloniaObject>(target);
_property = property; _property = property;
} }
@ -49,31 +49,23 @@ namespace Avalonia.Reactive
{ {
if (e.Property == _property) if (e.Property == _property)
{ {
if (e.Sender is AvaloniaObject ao) T newValue;
{
T newValue;
if (e is AvaloniaPropertyChangedEventArgs<T> typed)
{
newValue = AvaloniaObjectExtensions.GetValue(ao, typed.Property);
}
else
{
newValue = (T)e.Sender.GetValue(e.Property)!;
}
if (!_value.HasValue || if (e is AvaloniaPropertyChangedEventArgs<T> typed)
!EqualityComparer<T>.Default.Equals(newValue, _value.Value)) {
{ newValue = AvaloniaObjectExtensions.GetValue(e.Sender, typed.Property);
_value = newValue;
PublishNext(_value.Value!);
}
} }
else else
{ {
throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported."); newValue = (T)e.Sender.GetValue(e.Property)!;
} }
if (!_value.HasValue ||
!EqualityComparer<T>.Default.Equals(newValue, _value.Value))
{
_value = newValue;
PublishNext(_value.Value!);
}
} }
} }
} }

14
src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs

@ -45,7 +45,7 @@ public class CompositingRenderer : IRendererWithCompositor
_compositor = compositor; _compositor = compositor;
_recordingContext = new DrawingContext(_recorder); _recordingContext = new DrawingContext(_recorder);
CompositionTarget = compositor.CreateCompositionTarget(root.CreateRenderTarget); CompositionTarget = compositor.CreateCompositionTarget(root.CreateRenderTarget);
CompositionTarget.Root = ((Visual)root!.VisualRoot!).AttachToCompositor(compositor); CompositionTarget.Root = ((Visual)root).AttachToCompositor(compositor);
_update = Update; _update = Update;
} }
@ -75,7 +75,7 @@ public class CompositingRenderer : IRendererWithCompositor
} }
/// <inheritdoc/> /// <inheritdoc/>
public void AddDirty(IVisual visual) public void AddDirty(Visual visual)
{ {
if (_updating) if (_updating)
throw new InvalidOperationException("Visual was invalidated during the render pass"); throw new InvalidOperationException("Visual was invalidated during the render pass");
@ -84,7 +84,7 @@ public class CompositingRenderer : IRendererWithCompositor
} }
/// <inheritdoc/> /// <inheritdoc/>
public IEnumerable<IVisual> HitTest(Point p, IVisual root, Func<IVisual, bool>? filter) public IEnumerable<Visual> HitTest(Point p, Visual root, Func<Visual, bool>? filter)
{ {
Func<CompositionVisual, bool>? f = null; Func<CompositionVisual, bool>? f = null;
if (filter != null) if (filter != null)
@ -109,14 +109,14 @@ public class CompositingRenderer : IRendererWithCompositor
} }
/// <inheritdoc/> /// <inheritdoc/>
public IVisual? HitTestFirst(Point p, IVisual root, Func<IVisual, bool>? filter) public Visual? HitTestFirst(Point p, Visual root, Func<Visual, bool>? filter)
{ {
// TODO: Optimize // TODO: Optimize
return HitTest(p, root, filter).FirstOrDefault(); return HitTest(p, root, filter).FirstOrDefault();
} }
/// <inheritdoc/> /// <inheritdoc/>
public void RecalculateChildren(IVisual visual) public void RecalculateChildren(Visual visual)
{ {
if (_updating) if (_updating)
throw new InvalidOperationException("Visual was invalidated during the render pass"); throw new InvalidOperationException("Visual was invalidated during the render pass");
@ -130,9 +130,9 @@ public class CompositingRenderer : IRendererWithCompositor
if(v.CompositionVisual == null) if(v.CompositionVisual == null)
return; return;
var compositionChildren = v.CompositionVisual.Children; var compositionChildren = v.CompositionVisual.Children;
var visualChildren = (AvaloniaList<IVisual>)v.GetVisualChildren(); var visualChildren = (AvaloniaList<Visual>)v.GetVisualChildren();
PooledList<(IVisual visual, int index)>? sortedChildren = null; PooledList<(Visual visual, int index)>? sortedChildren = null;
if (v.HasNonUniformZIndexChildren && visualChildren.Count > 1) if (v.HasNonUniformZIndexChildren && visualChildren.Count > 1)
{ {
sortedChildren = new (visualChildren.Count); sortedChildren = new (visualChildren.Count);

22
src/Avalonia.Base/Rendering/DeferredRenderer.cs

@ -22,14 +22,14 @@ namespace Avalonia.Rendering
{ {
private readonly IDispatcher? _dispatcher; private readonly IDispatcher? _dispatcher;
private readonly IRenderLoop? _renderLoop; private readonly IRenderLoop? _renderLoop;
private readonly IVisual _root; private readonly Visual _root;
private readonly ISceneBuilder _sceneBuilder; private readonly ISceneBuilder _sceneBuilder;
private bool _running; private bool _running;
private bool _disposed; private bool _disposed;
private volatile IRef<Scene>? _scene; private volatile IRef<Scene>? _scene;
private DirtyVisuals? _dirty; private DirtyVisuals? _dirty;
private HashSet<IVisual>? _recalculateChildren; private HashSet<Visual>? _recalculateChildren;
private IRef<IRenderTargetBitmapImpl>? _overlay; private IRef<IRenderTargetBitmapImpl>? _overlay;
private int _lastSceneId = -1; private int _lastSceneId = -1;
private DisplayDirtyRects _dirtyRectsDisplay = new DisplayDirtyRects(); private DisplayDirtyRects _dirtyRectsDisplay = new DisplayDirtyRects();
@ -56,7 +56,7 @@ namespace Avalonia.Rendering
IDeferredRendererLock? rendererLock = null) : base(true) IDeferredRendererLock? rendererLock = null) : base(true)
{ {
_dispatcher = dispatcher ?? Dispatcher.UIThread; _dispatcher = dispatcher ?? Dispatcher.UIThread;
_root = root ?? throw new ArgumentNullException(nameof(root)); _root = root as Visual ?? throw new ArgumentNullException(nameof(root));
_sceneBuilder = sceneBuilder ?? new SceneBuilder(); _sceneBuilder = sceneBuilder ?? new SceneBuilder();
Layers = new RenderLayers(); Layers = new RenderLayers();
_renderLoop = renderLoop; _renderLoop = renderLoop;
@ -74,7 +74,7 @@ namespace Avalonia.Rendering
/// This constructor is intended to be used for unit testing. /// This constructor is intended to be used for unit testing.
/// </remarks> /// </remarks>
public DeferredRenderer( public DeferredRenderer(
IVisual root, Visual root,
IRenderTarget renderTarget, IRenderTarget renderTarget,
ISceneBuilder? sceneBuilder = null) : base(true) ISceneBuilder? sceneBuilder = null) : base(true)
{ {
@ -116,7 +116,7 @@ namespace Avalonia.Rendering
internal IRenderTarget? RenderTarget { get; private set; } internal IRenderTarget? RenderTarget { get; private set; }
/// <inheritdoc/> /// <inheritdoc/>
public void AddDirty(IVisual visual) public void AddDirty(Visual visual)
{ {
_dirty?.Add(visual); _dirty?.Add(visual);
} }
@ -142,7 +142,7 @@ namespace Avalonia.Rendering
DisposeRenderTarget(); DisposeRenderTarget();
} }
public void RecalculateChildren(IVisual visual) => _recalculateChildren?.Add(visual); public void RecalculateChildren(Visual visual) => _recalculateChildren?.Add(visual);
void DisposeRenderTarget() void DisposeRenderTarget()
{ {
@ -163,17 +163,17 @@ namespace Avalonia.Rendering
} }
/// <inheritdoc/> /// <inheritdoc/>
public IEnumerable<IVisual> HitTest(Point p, IVisual root, Func<IVisual, bool>? filter) public IEnumerable<Visual> HitTest(Point p, Visual root, Func<Visual, bool>? filter)
{ {
EnsureCanHitTest(); EnsureCanHitTest();
//It's safe to access _scene here without a lock since //It's safe to access _scene here without a lock since
//it's only changed from UI thread which we are currently on //it's only changed from UI thread which we are currently on
return _scene?.Item.HitTest(p, root, filter) ?? Enumerable.Empty<IVisual>(); return _scene?.Item.HitTest(p, root, filter) ?? Enumerable.Empty<Visual>();
} }
/// <inheritdoc/> /// <inheritdoc/>
public IVisual? HitTestFirst(Point p, IVisual root, Func<IVisual, bool>? filter) public Visual? HitTestFirst(Point p, Visual root, Func<Visual, bool>? filter)
{ {
EnsureCanHitTest(); EnsureCanHitTest();
@ -414,7 +414,7 @@ namespace Avalonia.Rendering
} }
private void Render(IDrawingContextImpl context, VisualNode node, IVisual? layer, Rect clipBounds) private void Render(IDrawingContextImpl context, VisualNode node, Visual? layer, Rect clipBounds)
{ {
if (layer == null || node.LayerRoot == layer) if (layer == null || node.LayerRoot == layer)
{ {
@ -643,7 +643,7 @@ namespace Avalonia.Rendering
if (_dirty == null) if (_dirty == null)
{ {
_dirty = new DirtyVisuals(); _dirty = new DirtyVisuals();
_recalculateChildren = new HashSet<IVisual>(); _recalculateChildren = new HashSet<Visual>();
_sceneBuilder.UpdateAll(scene); _sceneBuilder.UpdateAll(scene);
} }
else else

14
src/Avalonia.Base/Rendering/DirtyVisuals.cs

@ -13,10 +13,10 @@ namespace Avalonia.Rendering
/// visual. TODO: We probably want to put an upper limit on the number of visuals that can be /// visual. TODO: We probably want to put an upper limit on the number of visuals that can be
/// stored and if we reach that limit, assume all visuals are dirty. /// stored and if we reach that limit, assume all visuals are dirty.
/// </remarks> /// </remarks>
internal class DirtyVisuals : IEnumerable<IVisual> internal class DirtyVisuals : IEnumerable<Visual>
{ {
private SortedDictionary<int, List<IVisual>> _inner = new SortedDictionary<int, List<IVisual>>(); private SortedDictionary<int, List<Visual>> _inner = new SortedDictionary<int, List<Visual>>();
private Dictionary<IVisual, int> _index = new Dictionary<IVisual, int>(); private Dictionary<Visual, int> _index = new Dictionary<Visual, int>();
private int _enumerating; private int _enumerating;
/// <summary> /// <summary>
@ -28,14 +28,14 @@ namespace Avalonia.Rendering
/// Adds a visual to the dirty list. /// Adds a visual to the dirty list.
/// </summary> /// </summary>
/// <param name="visual">The dirty visual.</param> /// <param name="visual">The dirty visual.</param>
public void Add(IVisual visual) public void Add(Visual visual)
{ {
if (_enumerating > 0) if (_enumerating > 0)
{ {
throw new InvalidOperationException("Visual was invalidated during a render pass"); throw new InvalidOperationException("Visual was invalidated during a render pass");
} }
var distance = visual.CalculateDistanceFromAncestor(visual.VisualRoot); var distance = visual.CalculateDistanceFromAncestor((Visual?)visual.GetVisualRoot());
if (_index.TryGetValue(visual, out var existingDistance)) if (_index.TryGetValue(visual, out var existingDistance))
{ {
@ -50,7 +50,7 @@ namespace Avalonia.Rendering
if (!_inner.TryGetValue(distance, out var list)) if (!_inner.TryGetValue(distance, out var list))
{ {
list = new List<IVisual>(); list = new List<Visual>();
_inner.Add(distance, list); _inner.Add(distance, list);
} }
@ -76,7 +76,7 @@ namespace Avalonia.Rendering
/// Gets the dirty visuals, in ascending order of distance to their root. /// Gets the dirty visuals, in ascending order of distance to their root.
/// </summary> /// </summary>
/// <returns>A collection of visuals.</returns> /// <returns>A collection of visuals.</returns>
public IEnumerator<IVisual> GetEnumerator() public IEnumerator<Visual> GetEnumerator()
{ {
_enumerating++; _enumerating++;
try try

4
src/Avalonia.Base/Rendering/ICustomSimpleHitTest.cs

@ -24,10 +24,10 @@ namespace Avalonia.Rendering
public static class CustomSimpleHitTestExtensions public static class CustomSimpleHitTestExtensions
{ {
public static bool HitTestCustom(this IVisual visual, Point point) public static bool HitTestCustom(this Visual visual, Point point)
=> (visual as ICustomSimpleHitTest)?.HitTest(point) ?? visual.TransformedBounds?.Contains(point) == true; => (visual as ICustomSimpleHitTest)?.HitTest(point) ?? visual.TransformedBounds?.Contains(point) == true;
public static bool HitTestCustom(this IEnumerable<IVisual> children, Point point) public static bool HitTestCustom(this IEnumerable<Visual> children, Point point)
=> children.Any(ctrl => ctrl.HitTestCustom(point)); => children.Any(ctrl => ctrl.HitTestCustom(point));
} }
} }

2
src/Avalonia.Base/Rendering/IRenderRoot.cs

@ -8,7 +8,7 @@ namespace Avalonia.Rendering
/// Represents the root of a renderable tree. /// Represents the root of a renderable tree.
/// </summary> /// </summary>
[NotClientImplementable] [NotClientImplementable]
public interface IRenderRoot : IVisual public interface IRenderRoot
{ {
/// <summary> /// <summary>
/// Gets the client size of the window. /// Gets the client size of the window.

8
src/Avalonia.Base/Rendering/IRenderer.cs

@ -34,7 +34,7 @@ namespace Avalonia.Rendering
/// Mark a visual as dirty and needing re-rendering. /// Mark a visual as dirty and needing re-rendering.
/// </summary> /// </summary>
/// <param name="visual">The visual.</param> /// <param name="visual">The visual.</param>
void AddDirty(IVisual visual); void AddDirty(Visual visual);
/// <summary> /// <summary>
/// Hit tests a location to find the visuals at the specified point. /// Hit tests a location to find the visuals at the specified point.
@ -46,7 +46,7 @@ namespace Avalonia.Rendering
/// children will be excluded from the results. /// children will be excluded from the results.
/// </param> /// </param>
/// <returns>The visuals at the specified point, topmost first.</returns> /// <returns>The visuals at the specified point, topmost first.</returns>
IEnumerable<IVisual> HitTest(Point p, IVisual root, Func<IVisual, bool> filter); IEnumerable<Visual> HitTest(Point p, Visual root, Func<Visual, bool> filter);
/// <summary> /// <summary>
/// Hit tests a location to find first visual at the specified point. /// Hit tests a location to find first visual at the specified point.
@ -58,13 +58,13 @@ namespace Avalonia.Rendering
/// children will be excluded from the results. /// children will be excluded from the results.
/// </param> /// </param>
/// <returns>The visual at the specified point, topmost first.</returns> /// <returns>The visual at the specified point, topmost first.</returns>
IVisual? HitTestFirst(Point p, IVisual root, Func<IVisual, bool> filter); Visual? HitTestFirst(Point p, Visual root, Func<Visual, bool> filter);
/// <summary> /// <summary>
/// Informs the renderer that the z-ordering of a visual's children has changed. /// Informs the renderer that the z-ordering of a visual's children has changed.
/// </summary> /// </summary>
/// <param name="visual">The visual.</param> /// <param name="visual">The visual.</param>
void RecalculateChildren(IVisual visual); void RecalculateChildren(Visual visual);
/// <summary> /// <summary>
/// Called when a resize notification is received by the control being rendered. /// Called when a resize notification is received by the control being rendered.

38
src/Avalonia.Base/Rendering/ImmediateRenderer.cs

@ -18,7 +18,7 @@ namespace Avalonia.Rendering
/// </remarks> /// </remarks>
public class ImmediateRenderer : RendererBase, IRenderer, IVisualBrushRenderer public class ImmediateRenderer : RendererBase, IRenderer, IVisualBrushRenderer
{ {
private readonly IVisual _root; private readonly Visual _root;
private readonly IRenderRoot? _renderRoot; private readonly IRenderRoot? _renderRoot;
private bool _updateTransformedBounds = true; private bool _updateTransformedBounds = true;
private IRenderTarget? _renderTarget; private IRenderTarget? _renderTarget;
@ -27,13 +27,13 @@ namespace Avalonia.Rendering
/// Initializes a new instance of the <see cref="ImmediateRenderer"/> class. /// Initializes a new instance of the <see cref="ImmediateRenderer"/> class.
/// </summary> /// </summary>
/// <param name="root">The control to render.</param> /// <param name="root">The control to render.</param>
public ImmediateRenderer(IVisual root) public ImmediateRenderer(Visual root)
{ {
_root = root ?? throw new ArgumentNullException(nameof(root)); _root = root ?? throw new ArgumentNullException(nameof(root));
_renderRoot = root as IRenderRoot; _renderRoot = root as IRenderRoot;
} }
private ImmediateRenderer(IVisual root, bool updateTransformedBounds) private ImmediateRenderer(Visual root, bool updateTransformedBounds)
{ {
_root = root ?? throw new ArgumentNullException(nameof(root)); _root = root ?? throw new ArgumentNullException(nameof(root));
_renderRoot = root as IRenderRoot; _renderRoot = root as IRenderRoot;
@ -102,7 +102,7 @@ namespace Avalonia.Rendering
/// </summary> /// </summary>
/// <param name="visual">The visual.</param> /// <param name="visual">The visual.</param>
/// <param name="target">The render target.</param> /// <param name="target">The render target.</param>
public static void Render(IVisual visual, IRenderTarget target) public static void Render(Visual visual, IRenderTarget target)
{ {
using (var renderer = new ImmediateRenderer(visual, updateTransformedBounds: false)) using (var renderer = new ImmediateRenderer(visual, updateTransformedBounds: false))
using (var context = new DrawingContext(target.CreateDrawingContext(renderer))) using (var context = new DrawingContext(target.CreateDrawingContext(renderer)))
@ -116,7 +116,7 @@ namespace Avalonia.Rendering
/// </summary> /// </summary>
/// <param name="visual">The visual.</param> /// <param name="visual">The visual.</param>
/// <param name="context">The drawing context.</param> /// <param name="context">The drawing context.</param>
public static void Render(IVisual visual, DrawingContext context) public static void Render(Visual visual, DrawingContext context)
{ {
using (var renderer = new ImmediateRenderer(visual, updateTransformedBounds: false)) using (var renderer = new ImmediateRenderer(visual, updateTransformedBounds: false))
{ {
@ -125,7 +125,7 @@ namespace Avalonia.Rendering
} }
/// <inheritdoc/> /// <inheritdoc/>
public void AddDirty(IVisual visual) public void AddDirty(Visual visual)
{ {
if (visual.Bounds != Rect.Empty) if (visual.Bounds != Rect.Empty)
{ {
@ -162,18 +162,18 @@ namespace Avalonia.Rendering
} }
/// <inheritdoc/> /// <inheritdoc/>
public IEnumerable<IVisual> HitTest(Point p, IVisual root, Func<IVisual, bool> filter) public IEnumerable<Visual> HitTest(Point p, Visual root, Func<Visual, bool> filter)
{ {
return HitTest(root, p, filter); return HitTest(root, p, filter);
} }
public IVisual? HitTestFirst(Point p, IVisual root, Func<IVisual, bool> filter) public Visual? HitTestFirst(Point p, Visual root, Func<Visual, bool> filter)
{ {
return HitTest(root, p, filter).FirstOrDefault(); return HitTest(root, p, filter).FirstOrDefault();
} }
/// <inheritdoc/> /// <inheritdoc/>
public void RecalculateChildren(IVisual visual) => AddDirty(visual); public void RecalculateChildren(Visual visual) => AddDirty(visual);
/// <inheritdoc/> /// <inheritdoc/>
public void Start() public void Start()
@ -199,21 +199,21 @@ namespace Avalonia.Rendering
Render(new DrawingContext(context), visual, visual.Bounds); Render(new DrawingContext(context), visual, visual.Bounds);
} }
internal static void Render(IVisual visual, DrawingContext context, bool updateTransformedBounds) internal static void Render(Visual visual, DrawingContext context, bool updateTransformedBounds)
{ {
using var renderer = new ImmediateRenderer(visual, updateTransformedBounds); using var renderer = new ImmediateRenderer(visual, updateTransformedBounds);
renderer.Render(context, visual, visual.Bounds); renderer.Render(context, visual, visual.Bounds);
} }
private static void ClearTransformedBounds(IVisual visual) private static void ClearTransformedBounds(Visual visual)
{ {
foreach (var e in visual.GetSelfAndVisualDescendants()) foreach (var e in visual.GetSelfAndVisualDescendants())
{ {
visual.TransformedBounds = null; visual.SetTransformedBounds(null);
} }
} }
private static Rect GetTransformedBounds(IVisual visual) private static Rect GetTransformedBounds(Visual visual)
{ {
if (visual.RenderTransform == null) if (visual.RenderTransform == null)
{ {
@ -228,10 +228,10 @@ namespace Avalonia.Rendering
} }
} }
private static IEnumerable<IVisual> HitTest( private static IEnumerable<Visual> HitTest(
IVisual visual, Visual visual,
Point p, Point p,
Func<IVisual, bool>? filter) Func<Visual, bool>? filter)
{ {
_ = visual ?? throw new ArgumentNullException(nameof(visual)); _ = visual ?? throw new ArgumentNullException(nameof(visual));
@ -266,7 +266,7 @@ namespace Avalonia.Rendering
} }
} }
private void Render(DrawingContext context, IVisual visual, Rect clipRect) private void Render(DrawingContext context, Visual visual, Rect clipRect)
{ {
var opacity = visual.Opacity; var opacity = visual.Opacity;
var clipToBounds = visual.ClipToBounds; var clipToBounds = visual.ClipToBounds;
@ -329,11 +329,11 @@ namespace Avalonia.Rendering
#pragma warning restore 0618 #pragma warning restore 0618
if (_updateTransformedBounds) if (_updateTransformedBounds)
visual.TransformedBounds = transformed; visual.SetTransformedBounds(transformed);
var childrenEnumerable = visual.HasNonUniformZIndexChildren var childrenEnumerable = visual.HasNonUniformZIndexChildren
? visual.VisualChildren.OrderBy(x => x, ZIndexComparer.Instance) ? visual.VisualChildren.OrderBy(x => x, ZIndexComparer.Instance)
: (IEnumerable<IVisual>)visual.VisualChildren; : (IEnumerable<Visual>)visual.VisualChildren;
foreach (var child in childrenEnumerable) foreach (var child in childrenEnumerable)
{ {

4
src/Avalonia.Base/Rendering/RenderLayer.cs

@ -11,7 +11,7 @@ namespace Avalonia.Rendering
IDrawingContextImpl drawingContext, IDrawingContextImpl drawingContext,
Size size, Size size,
double scaling, double scaling,
IVisual layerRoot) Visual layerRoot)
{ {
Bitmap = RefCountable.Create(drawingContext.CreateLayer(size)); Bitmap = RefCountable.Create(drawingContext.CreateLayer(size));
Size = size; Size = size;
@ -24,7 +24,7 @@ namespace Avalonia.Rendering
public bool IsEmpty { get; set; } public bool IsEmpty { get; set; }
public double Scaling { get; private set; } public double Scaling { get; private set; }
public Size Size { get; private set; } public Size Size { get; private set; }
public IVisual LayerRoot { get; } public Visual LayerRoot { get; }
public void RecreateBitmap(IDrawingContextImpl drawingContext, Size size, double scaling) public void RecreateBitmap(IDrawingContextImpl drawingContext, Size size, double scaling)
{ {

7
src/Avalonia.Base/Rendering/RenderLayers.cs

@ -3,17 +3,16 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering.SceneGraph; using Avalonia.Rendering.SceneGraph;
using Avalonia.VisualTree;
namespace Avalonia.Rendering namespace Avalonia.Rendering
{ {
public class RenderLayers : IEnumerable<RenderLayer> public class RenderLayers : IEnumerable<RenderLayer>
{ {
private readonly List<RenderLayer> _inner = new List<RenderLayer>(); private readonly List<RenderLayer> _inner = new List<RenderLayer>();
private readonly Dictionary<IVisual, RenderLayer> _index = new Dictionary<IVisual, RenderLayer>(); private readonly Dictionary<Visual, RenderLayer> _index = new Dictionary<Visual, RenderLayer>();
public int Count => _inner.Count; public int Count => _inner.Count;
public RenderLayer this[IVisual layerRoot] => _index[layerRoot]; public RenderLayer this[Visual layerRoot] => _index[layerRoot];
public void Update(Scene scene, IDrawingContextImpl context) public void Update(Scene scene, IDrawingContextImpl context)
{ {
@ -59,7 +58,7 @@ namespace Avalonia.Rendering
_inner.Clear(); _inner.Clear();
} }
public bool TryGetValue(IVisual layerRoot, [NotNullWhen(true)] out RenderLayer? value) public bool TryGetValue(Visual layerRoot, [NotNullWhen(true)] out RenderLayer? value)
{ {
return _index.TryGetValue(layerRoot, out value); return _index.TryGetValue(layerRoot, out value);
} }

4
src/Avalonia.Base/Rendering/SceneGraph/ISceneBuilder.cs

@ -19,6 +19,6 @@ namespace Avalonia.Rendering.SceneGraph
/// <param name="scene">The scene.</param> /// <param name="scene">The scene.</param>
/// <param name="visual">The visual to update.</param> /// <param name="visual">The visual to update.</param>
/// <returns>True if changes were made, otherwise false.</returns> /// <returns>True if changes were made, otherwise false.</returns>
bool Update(Scene scene, IVisual visual); bool Update(Scene scene, Visual visual);
} }
} }

5
src/Avalonia.Base/Rendering/SceneGraph/IVisualNode.cs

@ -2,19 +2,18 @@
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Utilities; using Avalonia.Utilities;
using Avalonia.VisualTree;
namespace Avalonia.Rendering.SceneGraph namespace Avalonia.Rendering.SceneGraph
{ {
/// <summary> /// <summary>
/// Represents a node in the low-level scene graph representing an <see cref="IVisual"/>. /// Represents a node in the low-level scene graph representing a <see cref="Visual"/>.
/// </summary> /// </summary>
public interface IVisualNode : IDisposable public interface IVisualNode : IDisposable
{ {
/// <summary> /// <summary>
/// Gets the visual to which the node relates. /// Gets the visual to which the node relates.
/// </summary> /// </summary>
IVisual Visual { get; } Visual Visual { get; }
/// <summary> /// <summary>
/// Gets the parent scene graph node. /// Gets the parent scene graph node.

40
src/Avalonia.Base/Rendering/SceneGraph/Scene.cs

@ -13,24 +13,24 @@ namespace Avalonia.Rendering.SceneGraph
/// </summary> /// </summary>
public class Scene : IDisposable public class Scene : IDisposable
{ {
private readonly Dictionary<IVisual, IVisualNode> _index; private readonly Dictionary<Visual, IVisualNode> _index;
private readonly TaskCompletionSource<bool> _rendered = new TaskCompletionSource<bool>(); private readonly TaskCompletionSource<bool> _rendered = new TaskCompletionSource<bool>();
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Scene"/> class. /// Initializes a new instance of the <see cref="Scene"/> class.
/// </summary> /// </summary>
/// <param name="rootVisual">The root visual to draw.</param> /// <param name="rootVisual">The root visual to draw.</param>
public Scene(IVisual rootVisual) public Scene(Visual rootVisual)
: this( : this(
new VisualNode(rootVisual, null), new VisualNode(rootVisual, null),
new Dictionary<IVisual, IVisualNode>(), new Dictionary<Visual, IVisualNode>(),
new SceneLayers(rootVisual), new SceneLayers(rootVisual),
0) 0)
{ {
_index.Add(rootVisual, Root); _index.Add(rootVisual, Root);
} }
private Scene(VisualNode root, Dictionary<IVisual, IVisualNode> index, SceneLayers layers, int generation) private Scene(VisualNode root, Dictionary<Visual, IVisualNode> index, SceneLayers layers, int generation)
{ {
_ = root ?? throw new ArgumentNullException(nameof(root)); _ = root ?? throw new ArgumentNullException(nameof(root));
@ -87,7 +87,7 @@ namespace Avalonia.Rendering.SceneGraph
/// <returns>The cloned scene.</returns> /// <returns>The cloned scene.</returns>
public Scene CloneScene() public Scene CloneScene()
{ {
var index = new Dictionary<IVisual, IVisualNode>(_index.Count); var index = new Dictionary<Visual, IVisualNode>(_index.Count);
var root = Clone((VisualNode)Root, null, index); var root = Clone((VisualNode)Root, null, index);
var result = new Scene(root, index, Layers.Clone(), Generation + 1) var result = new Scene(root, index, Layers.Clone(), Generation + 1)
@ -115,7 +115,7 @@ namespace Avalonia.Rendering.SceneGraph
/// <returns> /// <returns>
/// The node representing the visual or null if it could not be found. /// The node representing the visual or null if it could not be found.
/// </returns> /// </returns>
public IVisualNode? FindNode(IVisual visual) public IVisualNode? FindNode(Visual visual)
{ {
_index.TryGetValue(visual, out var node); _index.TryGetValue(visual, out var node);
return node; return node;
@ -128,10 +128,10 @@ namespace Avalonia.Rendering.SceneGraph
/// <param name="root">The root of the subtree to search.</param> /// <param name="root">The root of the subtree to search.</param>
/// <param name="filter">A filter. May be null.</param> /// <param name="filter">A filter. May be null.</param>
/// <returns>The visuals at the specified point.</returns> /// <returns>The visuals at the specified point.</returns>
public IEnumerable<IVisual> HitTest(Point p, IVisual root, Func<IVisual, bool>? filter) public IEnumerable<Visual> HitTest(Point p, Visual root, Func<Visual, bool>? filter)
{ {
var node = FindNode(root); var node = FindNode(root);
return (node != null) ? new HitTestEnumerable(node, filter, p, Root) : Enumerable.Empty<IVisual>(); return (node != null) ? new HitTestEnumerable(node, filter, p, Root) : Enumerable.Empty<Visual>();
} }
/// <summary> /// <summary>
@ -141,7 +141,7 @@ namespace Avalonia.Rendering.SceneGraph
/// <param name="root">The root of the subtree to search.</param> /// <param name="root">The root of the subtree to search.</param>
/// <param name="filter">A filter. May be null.</param> /// <param name="filter">A filter. May be null.</param>
/// <returns>The visual at the specified point.</returns> /// <returns>The visual at the specified point.</returns>
public IVisual? HitTestFirst(Point p, IVisual root, Func<IVisual, bool>? filter) public Visual? HitTestFirst(Point p, Visual root, Func<Visual, bool>? filter)
{ {
var node = FindNode(root); var node = FindNode(root);
return (node != null) ? HitTestFirst(node, p, filter) : null; return (node != null) ? HitTestFirst(node, p, filter) : null;
@ -160,7 +160,7 @@ namespace Avalonia.Rendering.SceneGraph
node.Dispose(); node.Dispose();
} }
private VisualNode Clone(VisualNode source, IVisualNode? parent, Dictionary<IVisual, IVisualNode> index) private VisualNode Clone(VisualNode source, IVisualNode? parent, Dictionary<Visual, IVisualNode> index)
{ {
var result = source.Clone(parent); var result = source.Clone(parent);
@ -184,7 +184,7 @@ namespace Avalonia.Rendering.SceneGraph
return result; return result;
} }
private IVisual HitTestFirst(IVisualNode root, Point p, Func<IVisual, bool>? filter) private Visual HitTestFirst(IVisualNode root, Point p, Func<Visual, bool>? filter)
{ {
using var enumerator = new HitTestEnumerator(root, filter, p, Root); using var enumerator = new HitTestEnumerator(root, filter, p, Root);
@ -193,14 +193,14 @@ namespace Avalonia.Rendering.SceneGraph
return enumerator.Current; return enumerator.Current;
} }
private class HitTestEnumerable : IEnumerable<IVisual> private class HitTestEnumerable : IEnumerable<Visual>
{ {
private readonly IVisualNode _root; private readonly IVisualNode _root;
private readonly Func<IVisual, bool>? _filter; private readonly Func<Visual, bool>? _filter;
private readonly IVisualNode _sceneRoot; private readonly IVisualNode _sceneRoot;
private readonly Point _point; private readonly Point _point;
public HitTestEnumerable(IVisualNode root, Func<IVisual, bool>? filter, Point point, IVisualNode sceneRoot) public HitTestEnumerable(IVisualNode root, Func<Visual, bool>? filter, Point point, IVisualNode sceneRoot)
{ {
_root = root; _root = root;
_filter = filter; _filter = filter;
@ -208,7 +208,7 @@ namespace Avalonia.Rendering.SceneGraph
_sceneRoot = sceneRoot; _sceneRoot = sceneRoot;
} }
public IEnumerator<IVisual> GetEnumerator() public IEnumerator<Visual> GetEnumerator()
{ {
return new HitTestEnumerator(_root, _filter, _point, _sceneRoot); return new HitTestEnumerator(_root, _filter, _point, _sceneRoot);
} }
@ -219,15 +219,15 @@ namespace Avalonia.Rendering.SceneGraph
} }
} }
private struct HitTestEnumerator : IEnumerator<IVisual> private struct HitTestEnumerator : IEnumerator<Visual>
{ {
private readonly PooledStack<Entry> _nodeStack; private readonly PooledStack<Entry> _nodeStack;
private readonly Func<IVisual, bool>? _filter; private readonly Func<Visual, bool>? _filter;
private readonly IVisualNode _sceneRoot; private readonly IVisualNode _sceneRoot;
private IVisual? _current; private Visual? _current;
private readonly Point _point; private readonly Point _point;
public HitTestEnumerator(IVisualNode root, Func<IVisual, bool>? filter, Point point, IVisualNode sceneRoot) public HitTestEnumerator(IVisualNode root, Func<Visual, bool>? filter, Point point, IVisualNode sceneRoot)
{ {
_nodeStack = new PooledStack<Entry>(); _nodeStack = new PooledStack<Entry>();
_nodeStack.Push(new Entry(root, false, null, true)); _nodeStack.Push(new Entry(root, false, null, true));
@ -282,7 +282,7 @@ namespace Avalonia.Rendering.SceneGraph
throw new NotSupportedException(); throw new NotSupportedException();
} }
public IVisual Current => _current!; public Visual Current => _current!;
object IEnumerator.Current => Current; object IEnumerator.Current => Current;

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save