diff --git a/packages/Avalonia/AvaloniaBuildTasks.targets b/packages/Avalonia/AvaloniaBuildTasks.targets index 4f9c4d7720..1b822c14cf 100644 --- a/packages/Avalonia/AvaloniaBuildTasks.targets +++ b/packages/Avalonia/AvaloniaBuildTasks.targets @@ -74,7 +74,7 @@ ReportImportance="$(AvaloniaXamlReportImportance)"/> + Command="dotnet msbuild /nodereuse:false $(MSBuildProjectFile) /t:GenerateAvaloniaResources /p:_AvaloniaForceInternalMSBuild=true /p:Configuration=$(Configuration) /p:TargetFramework=$(TargetFramework) /p:RuntimeIdentifier=$(RuntimeIdentifier) /p:BuildProjectReferences=false"/> @@ -112,7 +112,7 @@ /> + Command="dotnet msbuild /nodereuse:false $(MSBuildProjectFile) /t:CompileAvaloniaXaml /p:_AvaloniaForceInternalMSBuild=true /p:Configuration=$(Configuration) /p:TargetFramework=$(TargetFramework) /p:RuntimeIdentifier=$(RuntimeIdentifier) /p:BuildProjectReferences=false"/> diff --git a/samples/BindingDemo/App.xaml b/samples/BindingDemo/App.xaml index 175e838616..5a8e65ed22 100644 --- a/samples/BindingDemo/App.xaml +++ b/samples/BindingDemo/App.xaml @@ -2,8 +2,14 @@ xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="BindingDemo.App"> - - - - + + + + + + + + + + diff --git a/samples/ControlCatalog/Pages/ExpanderPage.xaml b/samples/ControlCatalog/Pages/ExpanderPage.xaml index f0a80fd7ab..b5a2e6cdd0 100644 --- a/samples/ControlCatalog/Pages/ExpanderPage.xaml +++ b/samples/ControlCatalog/Pages/ExpanderPage.xaml @@ -52,6 +52,24 @@ Rounded + + + Expanded content + + + + + Expanded content + + diff --git a/samples/ControlCatalog/Pages/ExpanderPage.xaml.cs b/samples/ControlCatalog/Pages/ExpanderPage.xaml.cs index e8a080899a..98a494e533 100644 --- a/samples/ControlCatalog/Pages/ExpanderPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ExpanderPage.xaml.cs @@ -10,6 +10,12 @@ namespace ControlCatalog.Pages { this.InitializeComponent(); DataContext = new ExpanderPageViewModel(); + + var CollapsingDisabledExpander = this.Get("CollapsingDisabledExpander"); + var ExpandingDisabledExpander = this.Get("ExpandingDisabledExpander"); + + CollapsingDisabledExpander.Collapsing += (s, e) => { e.Handled = true; }; + ExpandingDisabledExpander.Expanding += (s, e) => { e.Handled = true; }; } private void InitializeComponent() diff --git a/samples/ControlCatalog/Pages/ListBoxPage.xaml b/samples/ControlCatalog/Pages/ListBoxPage.xaml index 04129cb82d..dc0eaf0a51 100644 --- a/samples/ControlCatalog/Pages/ListBoxPage.xaml +++ b/samples/ControlCatalog/Pages/ListBoxPage.xaml @@ -32,6 +32,7 @@ diff --git a/samples/ControlCatalog/Pages/TabControlPage.xaml b/samples/ControlCatalog/Pages/TabControlPage.xaml index a830ce69ac..e289de90f9 100644 --- a/samples/ControlCatalog/Pages/TabControlPage.xaml +++ b/samples/ControlCatalog/Pages/TabControlPage.xaml @@ -53,14 +53,8 @@ - - - - - - diff --git a/samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs b/samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs index 59489ebcc0..f89d9d1e20 100644 --- a/samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs +++ b/samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Reactive; using Avalonia.Controls; using Avalonia.Controls.Selection; +using ControlCatalog.Pages; using MiniMvvm; namespace ControlCatalog.ViewModels @@ -20,9 +21,9 @@ namespace ControlCatalog.ViewModels public ListBoxPageViewModel() { - Items = new ObservableCollection(Enumerable.Range(1, 10000).Select(i => GenerateItem())); + Items = new ObservableCollection(Enumerable.Range(1, 10000).Select(i => GenerateItem())); - Selection = new SelectionModel(); + Selection = new SelectionModel(); Selection.Select(1); _selectionMode = this.WhenAnyValue( @@ -58,8 +59,8 @@ namespace ControlCatalog.ViewModels }); } - public ObservableCollection Items { get; } - public SelectionModel Selection { get; } + public ObservableCollection Items { get; } + public SelectionModel Selection { get; } public IObservable SelectionMode => _selectionMode; public bool Multiple @@ -96,6 +97,31 @@ namespace ControlCatalog.ViewModels public MiniCommand RemoveItemCommand { get; } public MiniCommand SelectRandomItemCommand { get; } - private string GenerateItem() => $"Item {_counter++.ToString()}"; + private ItemModel GenerateItem() => new ItemModel(_counter ++); + } + + /// + /// An Item model for the + /// + public class ItemModel + { + /// + /// Creates a new ItemModel with the given ID + /// + /// The ID to display + public ItemModel(int id) + { + ID = id; + } + + /// + /// The ID of this Item + /// + public int ID { get; } + + public override string ToString() + { + return $"Item {ID}"; + } } } diff --git a/samples/ControlCatalog/ViewModels/TransitioningContentControlPageViewModel.cs b/samples/ControlCatalog/ViewModels/TransitioningContentControlPageViewModel.cs index 83892247e6..93857fd899 100644 --- a/samples/ControlCatalog/ViewModels/TransitioningContentControlPageViewModel.cs +++ b/samples/ControlCatalog/ViewModels/TransitioningContentControlPageViewModel.cs @@ -293,10 +293,10 @@ namespace ControlCatalog.ViewModels /// /// Any one of the parameters may be null, but not both. /// - private static IVisual GetVisualParent(IVisual? from, IVisual? to) + private static Visual GetVisualParent(Visual? from, Visual? to) { - var p1 = (from ?? to)!.VisualParent; - var p2 = (to ?? from)!.VisualParent; + var p1 = (from ?? to)!.GetVisualParent(); + var p2 = (to ?? from)!.GetVisualParent(); if (p1 != null && p2 != null && p1 != p2) { diff --git a/src/Android/Avalonia.Android/AndroidInputMethod.cs b/src/Android/Avalonia.Android/AndroidInputMethod.cs index b6adbde738..8d56086470 100644 --- a/src/Android/Avalonia.Android/AndroidInputMethod.cs +++ b/src/Android/Avalonia.Android/AndroidInputMethod.cs @@ -95,6 +95,10 @@ namespace Avalonia.Android _imm.UpdateSelection(_host, surroundingText.AnchorOffset, surroundingText.CursorOffset, surroundingText.AnchorOffset, surroundingText.CursorOffset); } + else + { + _imm.HideSoftInputFromWindow(_host.WindowToken, HideSoftInputFlags.ImplicitOnly); + } } private void SurroundingTextChanged(object sender, EventArgs e) diff --git a/src/Android/Avalonia.Android/AvaloniaMainActivity.cs b/src/Android/Avalonia.Android/AvaloniaMainActivity.cs index de8d02f188..b24581fb8b 100644 --- a/src/Android/Avalonia.Android/AvaloniaMainActivity.cs +++ b/src/Android/Avalonia.Android/AvaloniaMainActivity.cs @@ -4,9 +4,12 @@ using Android.Content; using Android.Content.Res; using Android.OS; using Android.Runtime; +using Android.Views; using AndroidX.AppCompat.App; using AndroidX.Lifecycle; +using AndroidRect = Android.Graphics.Rect; + namespace Avalonia.Android { public abstract class AvaloniaMainActivity : AppCompatActivity, IActivityResultHandler @@ -15,6 +18,7 @@ namespace Avalonia.Android public Action ActivityResult { get; set; } internal AvaloniaView View; + private GlobalLayoutListener _listener; protected override void OnCreate(Bundle savedInstanceState) { @@ -32,6 +36,10 @@ namespace Avalonia.Android base.OnCreate(savedInstanceState); SetContentView(View); + + _listener = new GlobalLayoutListener(View); + + View.ViewTreeObserver?.AddOnGlobalLayoutListener(_listener); } public object Content @@ -57,6 +65,8 @@ namespace Avalonia.Android { View.Content = null; + View.ViewTreeObserver?.RemoveOnGlobalLayoutListener(_listener); + base.OnDestroy(); } @@ -66,5 +76,20 @@ namespace Avalonia.Android ActivityResult?.Invoke(requestCode, resultCode, data); } + + class GlobalLayoutListener : Java.Lang.Object, ViewTreeObserver.IOnGlobalLayoutListener + { + private AvaloniaView _view; + + public GlobalLayoutListener(AvaloniaView view) + { + _view = view; + } + + public void OnGlobalLayout() + { + _view.TopLevelImpl?.Resize(_view.TopLevelImpl.ClientSize); + } + } } } diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 5f53eb36c8..4150b52946 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -26,6 +26,7 @@ using Avalonia.Rendering; using Avalonia.Rendering.Composition; using Java.Lang; using Math = System.Math; +using AndroidRect = Android.Graphics.Rect; namespace Avalonia.Android.Platform.SkiaPlatform { @@ -63,7 +64,21 @@ namespace Avalonia.Android.Platform.SkiaPlatform public IInputRoot InputRoot { get; private set; } - public virtual Size ClientSize => Size.ToSize(RenderScaling); + public virtual Size ClientSize + { + get + { + AndroidRect rect = new AndroidRect(); + AndroidRect intersection = new AndroidRect(); + + _view.GetWindowVisibleDisplayFrame(intersection); + _view.GetGlobalVisibleRect(rect); + + intersection.Intersect(rect); + + return new PixelSize(intersection.Right - intersection.Left, intersection.Bottom - intersection.Top).ToSize(RenderScaling); + } + } public Size? FrameSize => null; @@ -94,7 +109,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform ? new CompositingRenderer(root, AndroidPlatform.Compositor) : AndroidPlatform.Options.UseDeferredRendering ? new DeferredRenderer(root, AvaloniaLocator.Current.GetRequiredService()) { RenderOnlyOnRenderThread = true } - : new ImmediateRenderer(root); + : new ImmediateRenderer((Visual)root); public virtual void Hide() { @@ -149,6 +164,11 @@ namespace Avalonia.Android.Platform.SkiaPlatform Resized?.Invoke(size, PlatformResizeReason.Unspecified); } + internal void Resize(Size size) + { + Resized?.Invoke(size, PlatformResizeReason.Layout); + } + class ViewImpl : InvalidationAwareSurfaceView, ISurfaceHolderCallback, IInitEditorInfo { private readonly TopLevelImpl _tl; diff --git a/src/Avalonia.Base/Animation/PageSlide.cs b/src/Avalonia.Base/Animation/PageSlide.cs index b22bc8b243..609441ae8c 100644 --- a/src/Avalonia.Base/Animation/PageSlide.cs +++ b/src/Avalonia.Base/Animation/PageSlide.cs @@ -157,7 +157,7 @@ namespace Avalonia.Animation /// /// Any one of the parameters may be null, but not both. /// - protected static IVisual GetVisualParent(IVisual? from, IVisual? to) + protected static Visual GetVisualParent(Visual? from, Visual? to) { var p1 = (from ?? to)!.VisualParent; var p2 = (to ?? from)!.VisualParent; diff --git a/src/Avalonia.Base/AttachedProperty.cs b/src/Avalonia.Base/AttachedProperty.cs index 12c569c452..a43194153c 100644 --- a/src/Avalonia.Base/AttachedProperty.cs +++ b/src/Avalonia.Base/AttachedProperty.cs @@ -34,7 +34,7 @@ namespace Avalonia /// /// The owner type. /// The property. - public new AttachedProperty AddOwner() where TOwner : IAvaloniaObject + public new AttachedProperty AddOwner() where TOwner : AvaloniaObject { AvaloniaPropertyRegistry.Instance.Register(typeof(TOwner), this); return this; diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 68c8f19f19..a3a732428e 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -16,7 +16,7 @@ namespace Avalonia /// /// This class is analogous to DependencyObject in WPF. /// - public class AvaloniaObject : IAvaloniaObject, IAvaloniaObjectDebug, INotifyPropertyChanged + public class AvaloniaObject : IAvaloniaObjectDebug, INotifyPropertyChanged { private readonly ValueStore _values; private AvaloniaObject? _inheritanceParent; diff --git a/src/Avalonia.Base/AvaloniaObjectExtensions.cs b/src/Avalonia.Base/AvaloniaObjectExtensions.cs index 9644063da7..867d6215a5 100644 --- a/src/Avalonia.Base/AvaloniaObjectExtensions.cs +++ b/src/Avalonia.Base/AvaloniaObjectExtensions.cs @@ -36,7 +36,7 @@ namespace Avalonia /// /// The subscription to is created using a weak reference. /// - public static IObservable GetObservable(this IAvaloniaObject o, AvaloniaProperty property) + public static IObservable GetObservable(this AvaloniaObject o, AvaloniaProperty property) { return new AvaloniaPropertyObservable( o ?? throw new ArgumentNullException(nameof(o)), @@ -56,7 +56,7 @@ namespace Avalonia /// /// The subscription to is created using a weak reference. /// - public static IObservable GetObservable(this IAvaloniaObject o, AvaloniaProperty property) + public static IObservable GetObservable(this AvaloniaObject o, AvaloniaProperty property) { return new AvaloniaPropertyObservable( o ?? throw new ArgumentNullException(nameof(o)), @@ -76,7 +76,7 @@ namespace Avalonia /// The subscription to is created using a weak reference. /// public static IObservable> GetBindingObservable( - this IAvaloniaObject o, + this AvaloniaObject o, AvaloniaProperty property) { return new AvaloniaPropertyBindingObservable( @@ -98,7 +98,7 @@ namespace Avalonia /// The subscription to is created using a weak reference. /// public static IObservable> GetBindingObservable( - this IAvaloniaObject o, + this AvaloniaObject o, AvaloniaProperty property) { return new AvaloniaPropertyBindingObservable( @@ -115,11 +115,11 @@ namespace Avalonia /// The property. /// /// An observable which when subscribed pushes the property changed event args - /// each time a event is raised + /// each time a event is raised /// for the specified property. /// public static IObservable GetPropertyChangedObservable( - this IAvaloniaObject o, + this AvaloniaObject o, AvaloniaProperty property) { return new AvaloniaPropertyChangedObservable( @@ -140,7 +140,7 @@ namespace Avalonia /// property. /// public static ISubject GetSubject( - this IAvaloniaObject o, + this AvaloniaObject o, AvaloniaProperty property, BindingPriority priority = BindingPriority.LocalValue) { @@ -163,7 +163,7 @@ namespace Avalonia /// property. /// public static ISubject GetSubject( - this IAvaloniaObject o, + this AvaloniaObject o, AvaloniaProperty property, BindingPriority priority = BindingPriority.LocalValue) { @@ -185,7 +185,7 @@ namespace Avalonia /// property. /// public static ISubject> GetBindingSubject( - this IAvaloniaObject o, + this AvaloniaObject o, AvaloniaProperty property, BindingPriority priority = BindingPriority.LocalValue) { @@ -214,7 +214,7 @@ namespace Avalonia /// property. /// public static ISubject> GetBindingSubject( - this IAvaloniaObject o, + this AvaloniaObject o, AvaloniaProperty property, BindingPriority priority = BindingPriority.LocalValue) { @@ -241,7 +241,7 @@ namespace Avalonia /// A disposable which can be used to terminate the binding. /// public static IDisposable Bind( - this IAvaloniaObject target, + this AvaloniaObject target, AvaloniaProperty property, IObservable> source, BindingPriority priority = BindingPriority.LocalValue) @@ -250,17 +250,12 @@ namespace Avalonia property = property ?? throw new ArgumentNullException(nameof(property)); source = source ?? throw new ArgumentNullException(nameof(source)); - if (target is AvaloniaObject ao) + return property switch { - return property switch - { - StyledPropertyBase styled => ao.Bind(styled, source, priority), - DirectPropertyBase direct => ao.Bind(direct, source), - _ => throw new NotSupportedException("Unsupported AvaloniaProperty type."), - }; - } - - throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported."); + StyledPropertyBase styled => target.Bind(styled, source, priority), + DirectPropertyBase direct => target.Bind(direct, source), + _ => throw new NotSupportedException("Unsupported AvaloniaProperty type."), + }; } /// @@ -274,22 +269,17 @@ namespace Avalonia /// A disposable which can be used to terminate the binding. /// public static IDisposable Bind( - this IAvaloniaObject target, + this AvaloniaObject target, AvaloniaProperty property, IObservable source, BindingPriority priority = BindingPriority.LocalValue) { - if (target is AvaloniaObject ao) + return property switch { - return property switch - { - StyledPropertyBase styled => ao.Bind(styled, source, priority), - DirectPropertyBase direct => ao.Bind(direct, source), - _ => throw new NotSupportedException("Unsupported AvaloniaProperty type."), - }; - } - - throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported."); + StyledPropertyBase styled => target.Bind(styled, source, priority), + DirectPropertyBase direct => target.Bind(direct, source), + _ => throw new NotSupportedException("Unsupported AvaloniaProperty type."), + }; } /// @@ -306,7 +296,7 @@ namespace Avalonia /// /// An which can be used to cancel the binding. public static IDisposable Bind( - this IAvaloniaObject target, + this AvaloniaObject target, AvaloniaProperty property, IBinding binding, object? anchor = null) @@ -340,23 +330,17 @@ namespace Avalonia /// The object. /// The property. /// The value. - public static T GetValue(this IAvaloniaObject target, AvaloniaProperty property) + public static T GetValue(this AvaloniaObject target, AvaloniaProperty property) { target = target ?? throw new ArgumentNullException(nameof(target)); property = property ?? throw new ArgumentNullException(nameof(property)); - if (target is AvaloniaObject ao) + return property switch { - return property switch - { - StyledPropertyBase styled => ao.GetValue(styled), - DirectPropertyBase direct => ao.GetValue(direct), - _ => throw new NotSupportedException("Unsupported AvaloniaProperty type.") - }; - - } - - throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported."); + StyledPropertyBase styled => target.GetValue(styled), + DirectPropertyBase direct => target.GetValue(direct), + _ => throw new NotSupportedException("Unsupported AvaloniaProperty type.") + }; } /// @@ -372,15 +356,13 @@ namespace Avalonia /// For direct properties returns the current value of the property. /// public static object? GetBaseValue( - this IAvaloniaObject target, + this AvaloniaObject target, AvaloniaProperty property) { target = target ?? throw new ArgumentNullException(nameof(target)); property = property ?? throw new ArgumentNullException(nameof(property)); - if (target is AvaloniaObject ao) - return property.RouteGetBaseValue(ao); - throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported."); + return property.RouteGetBaseValue(target); } /// @@ -396,24 +378,18 @@ namespace Avalonia /// For direct properties returns the current value of the property. /// public static Optional GetBaseValue( - this IAvaloniaObject target, + this AvaloniaObject target, AvaloniaProperty property) { target = target ?? throw new ArgumentNullException(nameof(target)); property = property ?? throw new ArgumentNullException(nameof(property)); - if (target is AvaloniaObject ao) + return property switch { - return property switch - { - StyledPropertyBase styled => ao.GetBaseValue(styled), - DirectPropertyBase direct => ao.GetValue(direct), - _ => throw new NotSupportedException("Unsupported AvaloniaProperty type.") - }; - - } - - throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported."); + StyledPropertyBase styled => target.GetBaseValue(styled), + DirectPropertyBase direct => target.GetValue(direct), + _ => throw new NotSupportedException("Unsupported AvaloniaProperty type.") + }; } /// @@ -474,7 +450,7 @@ namespace Avalonia } public InstancedBinding? Initiate( - IAvaloniaObject target, + AvaloniaObject target, AvaloniaProperty? targetProperty, object? anchor = null, bool enableDataValidation = false) diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs index 59a61e3424..2c89062e51 100644 --- a/src/Avalonia.Base/AvaloniaProperty.cs +++ b/src/Avalonia.Base/AvaloniaProperty.cs @@ -38,7 +38,7 @@ namespace Avalonia Type valueType, Type ownerType, AvaloniaPropertyMetadata metadata, - Action? notifying = null) + Action? notifying = null) { _ = 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 /// callback is intended to support Control.IsDataContextChanging. /// - public Action? Notifying { get; } + public Action? Notifying { get; } /// /// Gets the integer ID that represents this property. @@ -177,7 +177,7 @@ namespace Avalonia { return new IndexerDescriptor { - Priority = BindingPriority.TemplatedParent, + Priority = BindingPriority.Template, Property = property, }; } @@ -238,9 +238,9 @@ namespace Avalonia bool inherits = false, BindingMode defaultBindingMode = BindingMode.OneWay, Func? validate = null, - Func? coerce = null, - Action? notifying = null) - where TOwner : IAvaloniaObject + Func? coerce = null, + Action? notifying = null) + where TOwner : AvaloniaObject { _ = name ?? throw new ArgumentNullException(nameof(name)); @@ -279,8 +279,8 @@ namespace Avalonia bool inherits = false, BindingMode defaultBindingMode = BindingMode.OneWay, Func? validate = null, - Func? coerce = null) - where THost : IAvaloniaObject + Func? coerce = null) + where THost : AvaloniaObject { _ = name ?? throw new ArgumentNullException(nameof(name)); @@ -316,8 +316,8 @@ namespace Avalonia bool inherits = false, BindingMode defaultBindingMode = BindingMode.OneWay, Func? validate = null, - Func? coerce = null) - where THost : IAvaloniaObject + Func? coerce = null) + where THost : AvaloniaObject { _ = name ?? throw new ArgumentNullException(nameof(name)); @@ -354,7 +354,7 @@ namespace Avalonia TValue unsetValue = default!, BindingMode defaultBindingMode = BindingMode.OneWay, bool enableDataValidation = false) - where TOwner : IAvaloniaObject + where TOwner : AvaloniaObject { _ = name ?? throw new ArgumentNullException(nameof(name)); _ = getter ?? throw new ArgumentNullException(nameof(getter)); @@ -415,7 +415,7 @@ namespace Avalonia /// /// The property metadata. /// - public AvaloniaPropertyMetadata GetMetadata() where T : IAvaloniaObject + public AvaloniaPropertyMetadata GetMetadata() where T : AvaloniaObject { return GetMetadata(typeof(T)); } diff --git a/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs b/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs index a3ca25bc45..997a363c26 100644 --- a/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs +++ b/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs @@ -9,7 +9,7 @@ namespace Avalonia public abstract class AvaloniaPropertyChangedEventArgs : EventArgs { public AvaloniaPropertyChangedEventArgs( - IAvaloniaObject sender, + AvaloniaObject sender, BindingPriority priority) { Sender = sender; @@ -18,7 +18,7 @@ namespace Avalonia } internal AvaloniaPropertyChangedEventArgs( - IAvaloniaObject sender, + AvaloniaObject sender, BindingPriority priority, bool isEffectiveValueChange) { @@ -31,7 +31,7 @@ namespace Avalonia /// Gets the that the property changed on. /// /// The sender object. - public IAvaloniaObject Sender { get; } + public AvaloniaObject Sender { get; } /// /// Gets the property that changed. diff --git a/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs`1.cs b/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs`1.cs index 2c7a597537..70c75d24e7 100644 --- a/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs`1.cs +++ b/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs`1.cs @@ -16,7 +16,7 @@ namespace Avalonia /// The new value of the property. /// The priority of the binding that produced the value. public AvaloniaPropertyChangedEventArgs( - IAvaloniaObject sender, + AvaloniaObject sender, AvaloniaProperty property, Optional oldValue, BindingValue newValue, @@ -26,7 +26,7 @@ namespace Avalonia } internal AvaloniaPropertyChangedEventArgs( - IAvaloniaObject sender, + AvaloniaObject sender, AvaloniaProperty property, Optional oldValue, BindingValue newValue, diff --git a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs index 62265d3c59..6106c58880 100644 --- a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs +++ b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs @@ -189,7 +189,7 @@ namespace Avalonia /// /// The object. /// A collection of definitions. - public IReadOnlyList GetRegistered(IAvaloniaObject o) + public IReadOnlyList GetRegistered(AvaloniaObject o) { _ = o ?? throw new ArgumentNullException(nameof(o)); @@ -205,7 +205,7 @@ namespace Avalonia /// The registered. /// public DirectPropertyBase GetRegisteredDirect( - IAvaloniaObject o, + AvaloniaObject o, DirectPropertyBase property) { return FindRegisteredDirect(o, property) ?? @@ -260,7 +260,7 @@ namespace Avalonia /// /// The property name contains a '.'. /// - public AvaloniaProperty? FindRegistered(IAvaloniaObject o, string name) + public AvaloniaProperty? FindRegistered(AvaloniaObject o, string name) { _ = o ?? throw new ArgumentNullException(nameof(o)); _ = name ?? throw new ArgumentNullException(nameof(name)); @@ -277,7 +277,7 @@ namespace Avalonia /// The registered property or null if no matching property found. /// public DirectPropertyBase? FindRegisteredDirect( - IAvaloniaObject o, + AvaloniaObject o, DirectPropertyBase property) { if (property.Owner == o.GetType()) @@ -362,7 +362,7 @@ namespace Avalonia /// The property. /// /// You won't usually want to call this method directly, instead use the - /// + /// /// method. /// public void Register(Type type, AvaloniaProperty property) @@ -413,7 +413,7 @@ namespace Avalonia /// The property. /// /// You won't usually want to call this method directly, instead use the - /// + /// /// method. /// public void RegisterAttached(Type type, AvaloniaProperty property) diff --git a/src/Avalonia.Base/AvaloniaProperty`1.cs b/src/Avalonia.Base/AvaloniaProperty`1.cs index 14acc2c47f..5a0d69f3bf 100644 --- a/src/Avalonia.Base/AvaloniaProperty`1.cs +++ b/src/Avalonia.Base/AvaloniaProperty`1.cs @@ -24,7 +24,7 @@ namespace Avalonia string name, Type ownerType, AvaloniaPropertyMetadata metadata, - Action? notifying = null) + Action? notifying = null) : base(name, typeof(TValue), ownerType, metadata, notifying) { _changed = new Subject>(); diff --git a/src/Avalonia.Base/ClassBindingManager.cs b/src/Avalonia.Base/ClassBindingManager.cs index e8b1cc301d..589b9b2d01 100644 --- a/src/Avalonia.Base/ClassBindingManager.cs +++ b/src/Avalonia.Base/ClassBindingManager.cs @@ -9,7 +9,7 @@ namespace Avalonia private static readonly Dictionary s_RegisteredProperties = new Dictionary(); - 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)) s_RegisteredProperties[className] = prop = RegisterClassProxyProperty(className); @@ -21,7 +21,7 @@ namespace Avalonia var prop = AvaloniaProperty.Register("__AvaloniaReserved::Classes::" + className); prop.Changed.Subscribe(args => { - var classes = ((IStyledElement)args.Sender).Classes; + var classes = ((StyledElement)args.Sender).Classes; classes.Set(className, args.NewValue.GetValueOrDefault()); }); diff --git a/src/Avalonia.Base/Controls/Classes.cs b/src/Avalonia.Base/Controls/Classes.cs index c3d3fbca46..30c6fdefa3 100644 --- a/src/Avalonia.Base/Controls/Classes.cs +++ b/src/Avalonia.Base/Controls/Classes.cs @@ -6,7 +6,7 @@ using Avalonia.Utilities; namespace Avalonia.Controls { /// - /// Holds a collection of style classes for an . + /// Holds a collection of style classes for an . /// /// /// Similar to CSS, each control may have any number of styling classes applied. diff --git a/src/Avalonia.Base/Controls/ISetInheritanceParent.cs b/src/Avalonia.Base/Controls/ISetInheritanceParent.cs index e85e025005..9c83c96e3f 100644 --- a/src/Avalonia.Base/Controls/ISetInheritanceParent.cs +++ b/src/Avalonia.Base/Controls/ISetInheritanceParent.cs @@ -17,6 +17,6 @@ namespace Avalonia.Controls /// Sets the control's inheritance parent. /// /// The parent. - void SetParent(IAvaloniaObject? parent); + void SetParent(AvaloniaObject? parent); } } diff --git a/src/Avalonia.Base/Controls/ResourceNodeExtensions.cs b/src/Avalonia.Base/Controls/ResourceNodeExtensions.cs index 6121646107..0bf1073098 100644 --- a/src/Avalonia.Base/Controls/ResourceNodeExtensions.cs +++ b/src/Avalonia.Base/Controls/ResourceNodeExtensions.cs @@ -2,6 +2,7 @@ using Avalonia.Data.Converters; using Avalonia.LogicalTree; using Avalonia.Reactive; +using Avalonia.Styling; #nullable enable @@ -49,7 +50,7 @@ namespace Avalonia.Controls return true; } - current = (current as IStyledElement)?.StylingParent as IResourceNode; + current = (current as IStyleHost)?.StylingParent as IResourceNode; } value = null; diff --git a/src/Avalonia.Base/Data/BindingOperations.cs b/src/Avalonia.Base/Data/BindingOperations.cs index 4e47a720f0..ceb3f71285 100644 --- a/src/Avalonia.Base/Data/BindingOperations.cs +++ b/src/Avalonia.Base/Data/BindingOperations.cs @@ -9,7 +9,7 @@ namespace Avalonia.Data public static readonly object DoNothing = new DoNothingType(); /// - /// Applies an a property on an . + /// Applies an a property on an . /// /// The target object. /// The property to bind. @@ -22,7 +22,7 @@ namespace Avalonia.Data /// /// An which can be used to cancel the binding. public static IDisposable Apply( - IAvaloniaObject target, + AvaloniaObject target, AvaloniaProperty property, InstancedBinding binding, object? anchor) diff --git a/src/Avalonia.Base/Data/BindingPriority.cs b/src/Avalonia.Base/Data/BindingPriority.cs index dd1654f53c..5fd5aae43b 100644 --- a/src/Avalonia.Base/Data/BindingPriority.cs +++ b/src/Avalonia.Base/Data/BindingPriority.cs @@ -1,7 +1,9 @@ +using System; + namespace Avalonia.Data { /// - /// The priority of a binding. + /// The priority of a value or binding. /// public enum BindingPriority { @@ -16,23 +18,22 @@ namespace Avalonia.Data LocalValue = 0, /// - /// A triggered style binding. + /// A triggered style value. /// /// /// A style trigger is a selector such as .class which overrides a - /// binding. In this way, a basic control can have - /// for example a Background from the templated parent which changes when the - /// control has the :pointerover class. + /// value. In this way, a control can have, e.g. a Background from + /// the template which changes when the control has the :pointerover class. /// StyleTrigger, /// - /// A binding to a property on the templated parent. + /// A value from the control's template. /// - TemplatedParent, + Template, /// - /// A style binding. + /// A style value. /// Style, @@ -42,8 +43,11 @@ namespace Avalonia.Data Inherited, /// - /// The binding is uninitialized. + /// The value is uninitialized. /// Unset = int.MaxValue, + + [Obsolete("Use Template priority")] + TemplatedParent = Template, } } diff --git a/src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs b/src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs index 92ecdafa0c..e340966983 100644 --- a/src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs +++ b/src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs @@ -24,7 +24,7 @@ namespace Avalonia.Data.Core { 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); return true; @@ -39,7 +39,7 @@ namespace Avalonia.Data.Core protected override void StartListeningCore(WeakReference reference) { - if (reference.TryGetTarget(out var target) && target is IAvaloniaObject obj) + if (reference.TryGetTarget(out var target) && target is AvaloniaObject obj) { _subscription = new AvaloniaPropertyObservable(obj, _property).Subscribe(ValueChanged); } diff --git a/src/Avalonia.Base/Data/IBinding.cs b/src/Avalonia.Base/Data/IBinding.cs index 7d44bf09b5..01204c39c1 100644 --- a/src/Avalonia.Base/Data/IBinding.cs +++ b/src/Avalonia.Base/Data/IBinding.cs @@ -24,7 +24,7 @@ namespace Avalonia.Data /// A or null if the binding could not be resolved. /// InstancedBinding? Initiate( - IAvaloniaObject target, + AvaloniaObject target, AvaloniaProperty? targetProperty, object? anchor = null, bool enableDataValidation = false); diff --git a/src/Avalonia.Base/Data/IndexerBinding.cs b/src/Avalonia.Base/Data/IndexerBinding.cs index 6a33d0c4fc..fcd179b9b2 100644 --- a/src/Avalonia.Base/Data/IndexerBinding.cs +++ b/src/Avalonia.Base/Data/IndexerBinding.cs @@ -3,7 +3,7 @@ public class IndexerBinding : IBinding { public IndexerBinding( - IAvaloniaObject source, + AvaloniaObject source, AvaloniaProperty property, BindingMode mode) { @@ -12,12 +12,12 @@ Mode = mode; } - private IAvaloniaObject Source { get; } + private AvaloniaObject Source { get; } public AvaloniaProperty Property { get; } private BindingMode Mode { get; } public InstancedBinding? Initiate( - IAvaloniaObject target, + AvaloniaObject target, AvaloniaProperty? targetProperty, object? anchor = null, bool enableDataValidation = false) diff --git a/src/Avalonia.Base/Data/InstancedBinding.cs b/src/Avalonia.Base/Data/InstancedBinding.cs index 9ff319eddb..a349486bf8 100644 --- a/src/Avalonia.Base/Data/InstancedBinding.cs +++ b/src/Avalonia.Base/Data/InstancedBinding.cs @@ -9,7 +9,7 @@ namespace Avalonia.Data /// /// Whereas an 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 - /// *instanced* by calling + /// *instanced* by calling /// on a target object. /// public class InstancedBinding diff --git a/src/Avalonia.Base/Diagnostics/IAvaloniaObjectDebug.cs b/src/Avalonia.Base/Diagnostics/IAvaloniaObjectDebug.cs index 2d88203f67..75c1e3432b 100644 --- a/src/Avalonia.Base/Diagnostics/IAvaloniaObjectDebug.cs +++ b/src/Avalonia.Base/Diagnostics/IAvaloniaObjectDebug.cs @@ -8,7 +8,7 @@ namespace Avalonia.Diagnostics public interface IAvaloniaObjectDebug { /// - /// Gets the subscriber list for the + /// Gets the subscriber list for the /// event. /// /// diff --git a/src/Avalonia.Base/DirectProperty.cs b/src/Avalonia.Base/DirectProperty.cs index a5905a6081..729240e5a1 100644 --- a/src/Avalonia.Base/DirectProperty.cs +++ b/src/Avalonia.Base/DirectProperty.cs @@ -15,7 +15,7 @@ namespace Avalonia /// allows the avalonia property system to read and write the current value. /// public class DirectProperty : DirectPropertyBase, IDirectPropertyAccessor - where TOwner : IAvaloniaObject + where TOwner : AvaloniaObject { /// /// Initializes a new instance of the class. @@ -151,13 +151,13 @@ namespace Avalonia } /// - internal override TValue InvokeGetter(IAvaloniaObject instance) + internal override TValue InvokeGetter(AvaloniaObject instance) { return Getter((TOwner)instance); } /// - internal override void InvokeSetter(IAvaloniaObject instance, BindingValue value) + internal override void InvokeSetter(AvaloniaObject instance, BindingValue value) { if (Setter == null) { @@ -171,13 +171,13 @@ namespace Avalonia } /// - object? IDirectPropertyAccessor.GetValue(IAvaloniaObject instance) + object? IDirectPropertyAccessor.GetValue(AvaloniaObject instance) { return Getter((TOwner)instance); } /// - void IDirectPropertyAccessor.SetValue(IAvaloniaObject instance, object? value) + void IDirectPropertyAccessor.SetValue(AvaloniaObject instance, object? value) { if (Setter == null) { diff --git a/src/Avalonia.Base/DirectPropertyBase.cs b/src/Avalonia.Base/DirectPropertyBase.cs index 0a8fe19885..ec9eba6d61 100644 --- a/src/Avalonia.Base/DirectPropertyBase.cs +++ b/src/Avalonia.Base/DirectPropertyBase.cs @@ -54,14 +54,14 @@ namespace Avalonia /// /// The instance. /// The property value. - internal abstract TValue InvokeGetter(IAvaloniaObject instance); + internal abstract TValue InvokeGetter(AvaloniaObject instance); /// /// Sets the value of the property on the instance. /// /// The instance. /// The value. - internal abstract void InvokeSetter(IAvaloniaObject instance, BindingValue value); + internal abstract void InvokeSetter(AvaloniaObject instance, BindingValue value); /// /// Gets the unset value for the property on the specified type. @@ -91,7 +91,7 @@ namespace Avalonia /// /// The type. /// The metadata. - public void OverrideMetadata(DirectPropertyMetadata metadata) where T : IAvaloniaObject + public void OverrideMetadata(DirectPropertyMetadata metadata) where T : AvaloniaObject { base.OverrideMetadata(typeof(T), metadata); } diff --git a/src/Avalonia.Base/IAvaloniaObject.cs b/src/Avalonia.Base/IAvaloniaObject.cs deleted file mode 100644 index 3b0016903b..0000000000 --- a/src/Avalonia.Base/IAvaloniaObject.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; -using Avalonia.Data; -using Avalonia.Metadata; - -namespace Avalonia -{ - /// - /// Interface for getting/setting values on an object. - /// - [NotClientImplementable] - public interface IAvaloniaObject - { - /// - /// Raised when a value changes on this object. - /// - event EventHandler? PropertyChanged; - - /// - /// Clears an 's local value. - /// - /// The property. - void ClearValue(AvaloniaProperty property); - - /// - /// Gets a value. - /// - /// The property. - /// The value. - object? GetValue(AvaloniaProperty property); - - /// - /// Checks whether a is animating. - /// - /// The property. - /// True if the property is animating, otherwise false. - bool IsAnimating(AvaloniaProperty property); - - /// - /// Checks whether a is set on this object. - /// - /// The property. - /// True if the property is set, otherwise false. - bool IsSet(AvaloniaProperty property); - - /// - /// Sets a value. - /// - /// The property. - /// The value. - /// The priority of the value. - /// - /// An if setting the property can be undone, otherwise null. - /// - IDisposable? SetValue( - AvaloniaProperty property, - object? value, - BindingPriority priority = BindingPriority.LocalValue); - - /// - /// Binds a to an observable. - /// - /// The property. - /// The observable. - /// The priority of the binding. - /// - /// A disposable which can be used to terminate the binding. - /// - IDisposable Bind( - AvaloniaProperty property, - IObservable source, - BindingPriority priority = BindingPriority.LocalValue); - - /// - /// Coerces the specified . - /// - /// The property. - void CoerceValue(AvaloniaProperty property); - } -} diff --git a/src/Avalonia.Base/IDataContextProvider.cs b/src/Avalonia.Base/IDataContextProvider.cs index 1d381cf4a5..79f9c97968 100644 --- a/src/Avalonia.Base/IDataContextProvider.cs +++ b/src/Avalonia.Base/IDataContextProvider.cs @@ -6,7 +6,7 @@ namespace Avalonia /// Defines an element with a data context that can be used for binding. /// [NotClientImplementable] - public interface IDataContextProvider : IAvaloniaObject + public interface IDataContextProvider { /// /// Gets or sets the element's data context. diff --git a/src/Avalonia.Base/IDirectPropertyAccessor.cs b/src/Avalonia.Base/IDirectPropertyAccessor.cs index 476a36f372..016ca9a6fb 100644 --- a/src/Avalonia.Base/IDirectPropertyAccessor.cs +++ b/src/Avalonia.Base/IDirectPropertyAccessor.cs @@ -24,13 +24,13 @@ namespace Avalonia /// /// The instance. /// The property value. - object? GetValue(IAvaloniaObject instance); + object? GetValue(AvaloniaObject instance); /// /// Sets the value of the property on the instance. /// /// The instance. /// The value. - void SetValue(IAvaloniaObject instance, object? value); + void SetValue(AvaloniaObject instance, object? value); } } diff --git a/src/Avalonia.Base/IStyledElement.cs b/src/Avalonia.Base/IStyledElement.cs deleted file mode 100644 index 4eed54de45..0000000000 --- a/src/Avalonia.Base/IStyledElement.cs +++ /dev/null @@ -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 - { - /// - /// Occurs when the control has finished initialization. - /// - event EventHandler? Initialized; - - /// - /// Gets a value that indicates whether the element has finished initialization. - /// - bool IsInitialized { get; } - - /// - /// Gets or sets the control's styling classes. - /// - new Classes Classes { get; set; } - - /// - /// Gets the control's logical parent. - /// - IStyledElement? Parent { get; } - } -} diff --git a/src/Avalonia.Base/Input/AccessKeyHandler.cs b/src/Avalonia.Base/Input/AccessKeyHandler.cs index 60175ae588..5695d27153 100644 --- a/src/Avalonia.Base/Input/AccessKeyHandler.cs +++ b/src/Avalonia.Base/Input/AccessKeyHandler.cs @@ -182,13 +182,13 @@ namespace Avalonia.Input // find all controls who have registered that access key. var text = e.Key.ToString().ToUpper(); var matches = _registered - .Where(x => x.Item1 == text && x.Item2.IsEffectivelyVisible) + .Where(x => x.Item1 == text && ((Visual)x.Item2).IsEffectivelyVisible) .Select(x => x.Item2); // If the menu is open, only match controls in the menu's visual tree. 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(); diff --git a/src/Avalonia.Base/Input/DragDropDevice.cs b/src/Avalonia.Base/Input/DragDropDevice.cs index 3c91856dcd..1e8035831e 100644 --- a/src/Avalonia.Base/Input/DragDropDevice.cs +++ b/src/Avalonia.Base/Input/DragDropDevice.cs @@ -13,7 +13,8 @@ namespace Avalonia.Input private Interactive? GetTarget(IInputRoot root, Point local) { - var target = root.InputHitTest(local)?.GetSelfAndVisualAncestors()?.OfType()?.FirstOrDefault(); + var hit = root.InputHitTest(local) as Visual; + var target = hit?.GetSelfAndVisualAncestors()?.OfType()?.FirstOrDefault(); if (target != null && DragDrop.GetAllowDrop(target)) return target; return null; @@ -24,7 +25,7 @@ namespace Avalonia.Input if (target == null) return DragDropEffects.None; - var p = inputRoot.TranslatePoint(point, target); + var p = ((Visual)inputRoot).TranslatePoint(point, target); if (!p.HasValue) return DragDropEffects.None; diff --git a/src/Avalonia.Base/Input/DragEventArgs.cs b/src/Avalonia.Base/Input/DragEventArgs.cs index 41276e2f06..7a27c53023 100644 --- a/src/Avalonia.Base/Input/DragEventArgs.cs +++ b/src/Avalonia.Base/Input/DragEventArgs.cs @@ -15,7 +15,7 @@ namespace Avalonia.Input public KeyModifiers KeyModifiers { get; private set; } - public Point GetPosition(IVisual relativeTo) + public Point GetPosition(Visual relativeTo) { var point = new Point(0, 0); diff --git a/src/Avalonia.Base/Input/FocusManager.cs b/src/Avalonia.Base/Input/FocusManager.cs index c7b5b8e27f..2bf666af44 100644 --- a/src/Avalonia.Base/Input/FocusManager.cs +++ b/src/Avalonia.Base/Input/FocusManager.cs @@ -185,7 +185,7 @@ namespace Avalonia.Input /// /// The element. /// True if the element can be focused. - private static bool CanFocus(IInputElement e) => e.Focusable && e.IsEffectivelyEnabled && e.IsVisible; + private static bool CanFocus(IInputElement e) => e.Focusable && e.IsEffectivelyEnabled && IsVisible(e); /// /// Gets the focus scope ancestors of the specified control, traversing popups. @@ -198,14 +198,15 @@ namespace Avalonia.Input while (c != null) { - var scope = c as IFocusScope; - - if (scope != null && c.VisualRoot?.IsVisible == true) + if (c is IFocusScope scope && + c is Visual v && + v.VisualRoot is Visual root && + root.IsVisible) { yield return scope; } - c = c.GetVisualParent() ?? + c = (c as Visual)?.GetVisualParent() ?? ((c as IHostedVisualTreeRoot)?.Host as IInputElement); } } @@ -221,11 +222,11 @@ namespace Avalonia.Input return; var ev = (PointerPressedEventArgs)e; - var visual = (IVisual)sender; + var visual = (Visual)sender; 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) { @@ -240,5 +241,7 @@ namespace Avalonia.Input } } } + + private static bool IsVisible(IInputElement e) => (e as Visual)?.IsVisible ?? true; } } diff --git a/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs b/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs index 54ef0b1a68..a202d6b5bc 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs @@ -35,8 +35,8 @@ namespace Avalonia.Input.GestureRecognizers if (_inputElement is ILogical logicalParent && recognizer is ISetLogicalParent logical) { logical.SetParent(logicalParent); - if (recognizer is IStyleable styleableRecognizer - && _inputElement is IStyleable styleableParent) + if (recognizer is StyledElement styleableRecognizer + && _inputElement is StyledElement styleableParent) styleableRecognizer.Bind(StyledElement.TemplatedParentProperty, styleableParent.GetObservable(StyledElement.TemplatedParentProperty)); } diff --git a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs index 4f5585ab6c..7bcb81767d 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs @@ -72,7 +72,7 @@ namespace Avalonia.Input.GestureRecognizers EndGesture(); _tracking = e.Pointer; _gestureId = ScrollGestureEventArgs.GetNextFreeId(); - _trackedRootPoint = e.GetPosition(_target); + _trackedRootPoint = e.GetPosition((Visual?)_target); } } @@ -86,7 +86,7 @@ namespace Avalonia.Input.GestureRecognizers { if (e.Pointer == _tracking) { - var rootPoint = e.GetPosition(_target); + var rootPoint = e.GetPosition((Visual?)_target); if (!_scrolling) { if (CanHorizontallyScroll && Math.Abs(_trackedRootPoint.X - rootPoint.X) > ScrollStartDistance) diff --git a/src/Avalonia.Base/Input/Gestures.cs b/src/Avalonia.Base/Input/Gestures.cs index 82ed96d982..496052ad35 100644 --- a/src/Avalonia.Base/Input/Gestures.cs +++ b/src/Avalonia.Base/Input/Gestures.cs @@ -43,7 +43,7 @@ namespace Avalonia.Input RoutedEvent.Register( "PointerSwipeGesture", RoutingStrategies.Bubble, typeof(Gestures)); - private static readonly WeakReference s_lastPress = new WeakReference(null); + private static readonly WeakReference s_lastPress = new WeakReference(null); private static Point s_lastPressPoint; static Gestures() @@ -52,32 +52,32 @@ namespace Avalonia.Input InputElement.PointerReleasedEvent.RouteFinished.Subscribe(PointerReleased); } - public static void AddTappedHandler(IInteractive element, EventHandler handler) + public static void AddTappedHandler(Interactive element, EventHandler handler) { element.AddHandler(TappedEvent, handler); } - public static void AddDoubleTappedHandler(IInteractive element, EventHandler handler) + public static void AddDoubleTappedHandler(Interactive element, EventHandler handler) { element.AddHandler(DoubleTappedEvent, handler); } - public static void AddRightTappedHandler(IInteractive element, EventHandler handler) + public static void AddRightTappedHandler(Interactive element, EventHandler handler) { element.AddHandler(RightTappedEvent, handler); } - public static void RemoveTappedHandler(IInteractive element, EventHandler handler) + public static void RemoveTappedHandler(Interactive element, EventHandler handler) { element.RemoveHandler(TappedEvent, handler); } - public static void RemoveDoubleTappedHandler(IInteractive element, EventHandler handler) + public static void RemoveDoubleTappedHandler(Interactive element, EventHandler handler) { element.RemoveHandler(DoubleTappedEvent, handler); } - public static void RemoveRightTappedHandler(IInteractive element, EventHandler handler) + public static void RemoveRightTappedHandler(Interactive element, EventHandler handler) { element.RemoveHandler(RightTappedEvent, handler); } @@ -92,20 +92,22 @@ namespace Avalonia.Input if (ev.Route == RoutingStrategies.Bubble) { var e = (PointerPressedEventArgs)ev; - var visual = (IVisual)ev.Source; + var visual = (Visual)ev.Source; if (e.ClickCount % 2 == 1) { s_isDoubleTapped = false; 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) { - 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; - 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) && 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(); var tapSize = settings?.GetTapSize(point.Pointer.Type) ?? new Size(4, 4); var tapRect = new Rect(s_lastPressPoint, new Size()) @@ -131,13 +134,13 @@ namespace Avalonia.Input { 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. //This behaviour matches UWP behaviour. else if (s_isDoubleTapped == false) { - e.Source.RaiseEvent(new TappedEventArgs(TappedEvent, e)); + i.RaiseEvent(new TappedEventArgs(TappedEvent, e)); } } } diff --git a/src/Avalonia.Base/Input/IInputElement.cs b/src/Avalonia.Base/Input/IInputElement.cs index 43ac87d008..6c20d20b4d 100644 --- a/src/Avalonia.Base/Input/IInputElement.cs +++ b/src/Avalonia.Base/Input/IInputElement.cs @@ -2,9 +2,6 @@ using System; using System.Collections.Generic; using Avalonia.Interactivity; using Avalonia.Metadata; -using Avalonia.VisualTree; - -#nullable enable namespace Avalonia.Input { @@ -12,7 +9,7 @@ namespace Avalonia.Input /// Defines input-related functionality for a control. /// [NotClientImplementable] - public interface IInputElement : IInteractive, IVisual + public interface IInputElement { /// /// Occurs when the control receives focus. @@ -93,7 +90,12 @@ namespace Avalonia.Input /// value of this control and its parent controls. /// bool IsEffectivelyEnabled { get; } - + + /// + /// Gets a value indicating whether this control and all its parents are visible. + /// + bool IsEffectivelyVisible { get; } + /// /// Gets a value indicating whether keyboard focus is anywhere within the element or its visual tree child elements. /// @@ -123,5 +125,32 @@ namespace Avalonia.Input /// Gets the key bindings for the element. /// List KeyBindings { get; } + + /// + /// Adds a handler for the specified routed event. + /// + /// The routed event. + /// The handler. + /// The routing strategies to listen to. + /// Whether handled events should also be listened for. + /// A disposable that terminates the event subscription. + void AddHandler( + RoutedEvent routedEvent, + Delegate handler, + RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble, + bool handledEventsToo = false); + + /// + /// Removes a handler for the specified routed event. + /// + /// The routed event. + /// The handler. + void RemoveHandler(RoutedEvent routedEvent, Delegate handler); + + /// + /// Raises a routed event. + /// + /// The event args. + void RaiseEvent(RoutedEventArgs e); } } diff --git a/src/Avalonia.Base/Input/IMainMenu.cs b/src/Avalonia.Base/Input/IMainMenu.cs index 213a979c28..0010397c1a 100644 --- a/src/Avalonia.Base/Input/IMainMenu.cs +++ b/src/Avalonia.Base/Input/IMainMenu.cs @@ -9,7 +9,7 @@ namespace Avalonia.Input /// Defines the interface for a window's main menu. /// [NotClientImplementable] - public interface IMainMenu : IVisual + public interface IMainMenu { /// /// Gets a value indicating whether the menu is open. diff --git a/src/Avalonia.Base/Input/InputExtensions.cs b/src/Avalonia.Base/Input/InputExtensions.cs index cbe36583e6..73dcef9e95 100644 --- a/src/Avalonia.Base/Input/InputExtensions.cs +++ b/src/Avalonia.Base/Input/InputExtensions.cs @@ -12,7 +12,7 @@ namespace Avalonia.Input /// public static class InputExtensions { - private static readonly Func s_hitTestDelegate = IsHitTestVisible; + private static readonly Func s_hitTestDelegate = IsHitTestVisible; /// /// Returns the active input elements at a point on an . @@ -26,7 +26,8 @@ namespace Avalonia.Input { element = element ?? throw new ArgumentNullException(nameof(element)); - return element.GetVisualsAt(p, s_hitTestDelegate).Cast(); + return (element as Visual)?.GetVisualsAt(p, s_hitTestDelegate).Cast() ?? + Enumerable.Empty(); } /// @@ -39,7 +40,7 @@ namespace Avalonia.Input { 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; } /// @@ -55,22 +56,22 @@ namespace Avalonia.Input public static IInputElement? InputHitTest( this IInputElement element, Point p, - Func filter) + Func filter) { element = element ?? throw new ArgumentNullException(nameof(element)); 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; return element != null && - element.IsVisible && + visual.IsVisible && element.IsHitTestVisible && element.IsEffectivelyEnabled && - element.IsAttachedToVisualTree; + visual.IsAttachedToVisualTree; } } } diff --git a/src/Avalonia.Base/Input/KeyboardDevice.cs b/src/Avalonia.Base/Input/KeyboardDevice.cs index 0600b54618..9d23cc49d9 100644 --- a/src/Avalonia.Base/Input/KeyboardDevice.cs +++ b/src/Avalonia.Base/Input/KeyboardDevice.cs @@ -3,7 +3,6 @@ using System.Runtime.CompilerServices; using Avalonia.Input.Raw; using Avalonia.Input.TextInput; using Avalonia.Interactivity; -using Avalonia.VisualTree; namespace Avalonia.Input { @@ -37,18 +36,21 @@ namespace Avalonia.Input ie.IsKeyboardFocusWithin = false; } - el = (IInputElement?)el.VisualParent; + el = (IInputElement?)(el as Visual)?.VisualParent; } } 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); - break; + if (visual is IInputElement el && el.IsKeyboardFocusWithin) + { + ClearFocusWithin(el, true); + break; + } } } @@ -81,7 +83,7 @@ namespace Avalonia.Input break; } - el = el.VisualParent as IInputElement; + el = (el as Visual)?.VisualParent as IInputElement; } el = oldElement; @@ -100,18 +102,21 @@ namespace Avalonia.Input ie.IsKeyboardFocusWithin = true; } - el = el.VisualParent as IInputElement; + el = (el as Visual)?.VisualParent as IInputElement; } } 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); - break; + if (visual is IInputElement el && el.IsKeyboardFocusWithin) + { + ClearChildrenFocusWithin(el, true); + break; + } } } @@ -128,11 +133,11 @@ namespace Avalonia.Input { if (element != FocusedElement) { - var interactive = FocusedElement as IInteractive; + var interactive = FocusedElement as Interactive; if (FocusedElement != null && - (!FocusedElement.IsAttachedToVisualTree || - _focusedRoot != element?.VisualRoot as IInputRoot) && + (!((Visual)FocusedElement).IsAttachedToVisualTree || + _focusedRoot != ((Visual?)element)?.VisualRoot as IInputRoot) && _focusedRoot != null) { ClearChildrenFocusWithin(_focusedRoot, true); @@ -140,14 +145,14 @@ namespace Avalonia.Input SetIsFocusWithin(FocusedElement, element); _focusedElement = element; - _focusedRoot = _focusedElement?.VisualRoot as IInputRoot; + _focusedRoot = ((Visual?)_focusedElement)?.VisualRoot as IInputRoot; interactive?.RaiseEvent(new RoutedEventArgs { RoutedEvent = InputElement.LostFocusEvent, }); - interactive = element as IInteractive; + interactive = element as Interactive; interactive?.RaiseEvent(new GotFocusEventArgs { @@ -191,8 +196,8 @@ namespace Avalonia.Input KeyModifiers = keyInput.Modifiers.ToKeyModifiers(), Source = element, }; - - IVisual? currentHandler = element; + + var currentHandler = element as Visual; while (currentHandler != null && !ev.Handled && keyInput.Type == RawKeyEventType.KeyDown) { var bindings = (currentHandler as IInputElement)?.KeyBindings; diff --git a/src/Avalonia.Base/Input/KeyboardNavigation.cs b/src/Avalonia.Base/Input/KeyboardNavigation.cs index 042a9adf5f..9d989d96d6 100644 --- a/src/Avalonia.Base/Input/KeyboardNavigation.cs +++ b/src/Avalonia.Base/Input/KeyboardNavigation.cs @@ -68,7 +68,7 @@ namespace Avalonia.Input /// The tab index. public static void SetTabIndex(IInputElement element, int value) { - ((IAvaloniaObject)element).SetValue(TabIndexProperty, value); + ((AvaloniaObject)element).SetValue(TabIndexProperty, value); } /// diff --git a/src/Avalonia.Base/Input/KeyboardNavigationHandler.cs b/src/Avalonia.Base/Input/KeyboardNavigationHandler.cs index f73203f6b7..b05d8f30bb 100644 --- a/src/Avalonia.Base/Input/KeyboardNavigationHandler.cs +++ b/src/Avalonia.Base/Input/KeyboardNavigationHandler.cs @@ -50,7 +50,7 @@ namespace Avalonia.Input element = element ?? throw new ArgumentNullException(nameof(element)); // If there's a custom keyboard navigation handler as an ancestor, use that. - var custom = element.FindAncestorOfType(true); + var custom = (element as Visual)?.FindAncestorOfType(true); if (custom is object && HandlePreCustomNavigation(custom, element, direction, out var ce)) return ce; @@ -156,9 +156,9 @@ namespace Avalonia.Input NavigationDirection direction, [NotNullWhen(true)] out IInputElement? result) { - if (newElement is object) + if (newElement is Visual v) { - var customHandler = newElement.FindAncestorOfType(true); + var customHandler = v.FindAncestorOfType(true); if (customHandler is object) { diff --git a/src/Avalonia.Base/Input/MouseDevice.cs b/src/Avalonia.Base/Input/MouseDevice.cs index 8c10799911..71c1b2f145 100644 --- a/src/Avalonia.Base/Input/MouseDevice.cs +++ b/src/Avalonia.Base/Input/MouseDevice.cs @@ -141,7 +141,7 @@ namespace Avalonia.Input _lastClickRect = new Rect(p, new Size()) .Inflate(new Thickness(doubleClickSize.Width / 2, doubleClickSize.Height / 2)); _lastMouseDownButton = properties.PointerUpdateKind.GetMouseButton(); - var e = new PointerPressedEventArgs(source, _pointer, root, p, timestamp, properties, inputModifiers, _clickCount); + var e = new PointerPressedEventArgs(source, _pointer, (Visual)root, p, timestamp, properties, inputModifiers, _clickCount); source.RaiseEvent(e); return e.Handled; } @@ -161,7 +161,7 @@ namespace Avalonia.Input 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); source.RaiseEvent(e); @@ -181,7 +181,7 @@ namespace Avalonia.Input 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); source?.RaiseEvent(e); @@ -204,7 +204,7 @@ namespace Avalonia.Input 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); return e.Handled; @@ -224,7 +224,7 @@ namespace Avalonia.Input if (source != null) { 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); return e.Handled; @@ -244,7 +244,7 @@ namespace Avalonia.Input if (source != null) { 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); return e.Handled; @@ -264,7 +264,7 @@ namespace Avalonia.Input if (source != null) { 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); return e.Handled; diff --git a/src/Avalonia.Base/Input/Navigation/FocusExtensions.cs b/src/Avalonia.Base/Input/Navigation/FocusExtensions.cs index 83bcd9187a..3cd4ca0d76 100644 --- a/src/Avalonia.Base/Input/Navigation/FocusExtensions.cs +++ b/src/Avalonia.Base/Input/Navigation/FocusExtensions.cs @@ -10,13 +10,21 @@ namespace Avalonia.Input.Navigation /// /// The element. /// True if the element can be focused. - 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; + } /// /// Checks if descendants of the specified element can be focused. /// /// The element. /// True if descendants of the element can be focused. - 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; + } } } diff --git a/src/Avalonia.Base/Input/Navigation/TabNavigation.cs b/src/Avalonia.Base/Input/Navigation/TabNavigation.cs index cfd45f8262..d218867cf2 100644 --- a/src/Avalonia.Base/Input/Navigation/TabNavigation.cs +++ b/src/Avalonia.Base/Input/Navigation/TabNavigation.cs @@ -212,9 +212,10 @@ namespace Avalonia.Input.Navigation if (!IsFocusScope(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 && - e.IsVisualAncestorOf(visualFocusedElement)) + v.IsVisualAncestorOf(visualFocusedElement)) { return focusedElement; } @@ -236,7 +237,7 @@ namespace Avalonia.Input.Navigation if (uiElement is null || IsVisibleAndEnabled(uiElement)) { - if (e is IVisual elementAsVisual) + if (e is Visual elementAsVisual) { var children = elementAsVisual.VisualChildren; var count = children.Count; @@ -272,7 +273,7 @@ namespace Avalonia.Input.Navigation if (uiElement == null || IsVisibleAndEnabled(uiElement)) { - var elementAsVisual = e as IVisual; + var elementAsVisual = e as Visual; if (elementAsVisual != null) { @@ -385,7 +386,7 @@ namespace Avalonia.Input.Navigation 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 count = children.Count; @@ -589,7 +590,7 @@ namespace Avalonia.Input.Navigation 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 count = children.Count; @@ -646,7 +647,7 @@ namespace Avalonia.Input.Navigation private static IInputElement? GetParent(IInputElement e) { // 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(); // 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 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; } } diff --git a/src/Avalonia.Base/Input/PenDevice.cs b/src/Avalonia.Base/Input/PenDevice.cs index 31f34566be..d7d9bf8902 100644 --- a/src/Avalonia.Base/Input/PenDevice.cs +++ b/src/Avalonia.Base/Input/PenDevice.cs @@ -91,7 +91,7 @@ namespace Avalonia.Input _lastClickRect = new Rect(p, new Size()) .Inflate(new Thickness(doubleClickSize.Width / 2, doubleClickSize.Height / 2)); _lastMouseDownButton = properties.PointerUpdateKind.GetMouseButton(); - var e = new PointerPressedEventArgs(source, pointer, root, p, timestamp, properties, inputModifiers, _clickCount); + var e = new PointerPressedEventArgs(source, pointer, (Visual)root, p, timestamp, properties, inputModifiers, _clickCount); source.RaiseEvent(e); return e.Handled; } @@ -108,7 +108,7 @@ namespace Avalonia.Input 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); source.RaiseEvent(e); @@ -126,7 +126,7 @@ namespace Avalonia.Input 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); source?.RaiseEvent(e); diff --git a/src/Avalonia.Base/Input/Pointer.cs b/src/Avalonia.Base/Input/Pointer.cs index 3012f07f6a..25adc359a2 100644 --- a/src/Avalonia.Base/Input/Pointer.cs +++ b/src/Avalonia.Base/Input/Pointer.cs @@ -21,10 +21,10 @@ namespace Avalonia.Input IInputElement? FindCommonParent(IInputElement? control1, IInputElement? control2) { - if (control1 == null || control2 == null) + if (control1 is not Visual c1 || control2 is not Visual c2) return null; - var seen = new HashSet(control1.GetSelfAndVisualAncestors().OfType()); - return control2.GetSelfAndVisualAncestors().OfType().FirstOrDefault(seen.Contains); + var seen = new HashSet(c1.GetSelfAndVisualAncestors().OfType()); + return c2.GetSelfAndVisualAncestors().OfType().FirstOrDefault(seen.Contains); } protected virtual void PlatformCapture(IInputElement? element) @@ -34,15 +34,15 @@ namespace Avalonia.Input public void Capture(IInputElement? control) { - if (Captured != null) - Captured.DetachedFromVisualTree -= OnCaptureDetached; + if (Captured is Visual v1) + v1.DetachedFromVisualTree -= OnCaptureDetached; var oldCapture = Captured; Captured = control; PlatformCapture(control); - if (oldCapture != null) + if (oldCapture is Visual v2) { var commonParent = FindCommonParent(control, oldCapture); - foreach (var notifyTarget in oldCapture.GetSelfAndVisualAncestors().OfType()) + foreach (var notifyTarget in v2.GetSelfAndVisualAncestors().OfType()) { if (notifyTarget == commonParent) break; @@ -50,11 +50,11 @@ namespace Avalonia.Input } } - if (Captured != null) - Captured.DetachedFromVisualTree += OnCaptureDetached; + if (Captured is Visual v3) + v3.DetachedFromVisualTree += OnCaptureDetached; } - IInputElement? GetNextCapture(IVisual parent) + IInputElement? GetNextCapture(Visual parent) { return parent as IInputElement ?? parent.FindAncestorOfType(); } diff --git a/src/Avalonia.Base/Input/PointerDeltaEventArgs.cs b/src/Avalonia.Base/Input/PointerDeltaEventArgs.cs index d5577d77af..af0fa83382 100644 --- a/src/Avalonia.Base/Input/PointerDeltaEventArgs.cs +++ b/src/Avalonia.Base/Input/PointerDeltaEventArgs.cs @@ -7,8 +7,8 @@ namespace Avalonia.Input { public Vector Delta { get; set; } - internal PointerDeltaEventArgs(RoutedEvent routedEvent, IInteractive? source, - IPointer pointer, IVisual rootVisual, Point rootVisualPosition, ulong timestamp, + internal PointerDeltaEventArgs(RoutedEvent routedEvent, object? source, + IPointer pointer, Visual rootVisual, Point rootVisualPosition, ulong timestamp, PointerPointProperties properties, KeyModifiers modifiers, Vector delta) : base(routedEvent, source, pointer, rootVisual, rootVisualPosition, timestamp, properties, modifiers) diff --git a/src/Avalonia.Base/Input/PointerEventArgs.cs b/src/Avalonia.Base/Input/PointerEventArgs.cs index f84dec42cb..d736253728 100644 --- a/src/Avalonia.Base/Input/PointerEventArgs.cs +++ b/src/Avalonia.Base/Input/PointerEventArgs.cs @@ -8,15 +8,15 @@ namespace Avalonia.Input { public class PointerEventArgs : RoutedEventArgs { - private readonly IVisual? _rootVisual; + private readonly Visual? _rootVisual; private readonly Point _rootVisualPosition; private readonly PointerPointProperties _properties; private readonly Lazy?>? _previousPoints; internal PointerEventArgs(RoutedEvent routedEvent, - IInteractive? source, + object? source, IPointer pointer, - IVisual? rootVisual, Point rootVisualPosition, + Visual? rootVisual, Point rootVisualPosition, ulong timestamp, PointerPointProperties properties, KeyModifiers modifiers) @@ -30,11 +30,11 @@ namespace Avalonia.Input Timestamp = timestamp; KeyModifiers = modifiers; } - + internal PointerEventArgs(RoutedEvent routedEvent, - IInteractive? source, + object? source, IPointer pointer, - IVisual? rootVisual, Point rootVisualPosition, + Visual? rootVisual, Point rootVisualPosition, ulong timestamp, PointerPointProperties properties, KeyModifiers modifiers, @@ -59,7 +59,7 @@ namespace Avalonia.Input /// public KeyModifiers KeyModifiers { get; } - private Point GetPosition(Point pt, IVisual? relativeTo) + private Point GetPosition(Point pt, Visual? relativeTo) { if (_rootVisual == null) return default; @@ -74,14 +74,14 @@ namespace Avalonia.Input /// /// The control. /// The pointer position in the control's coordinates. - public Point GetPosition(IVisual? relativeTo) => GetPosition(_rootVisualPosition, relativeTo); + public Point GetPosition(Visual? relativeTo) => GetPosition(_rootVisualPosition, relativeTo); /// /// Returns the PointerPoint associated with the current event /// /// The visual which coordinate system to use. Pass null for toplevel coordinate system /// - public PointerPoint GetCurrentPoint(IVisual? relativeTo) + public PointerPoint GetCurrentPoint(Visual? relativeTo) => new PointerPoint(Pointer, GetPosition(relativeTo), _properties); /// @@ -89,7 +89,7 @@ namespace Avalonia.Input /// /// The visual which coordinate system to use. Pass null for toplevel coordinate system /// - public IReadOnlyList GetIntermediatePoints(IVisual? relativeTo) + public IReadOnlyList GetIntermediatePoints(Visual? relativeTo) { var previousPoints = _previousPoints?.Value; if (previousPoints == null || previousPoints.Count == 0) @@ -125,9 +125,9 @@ namespace Avalonia.Input public class PointerPressedEventArgs : PointerEventArgs { internal PointerPressedEventArgs( - IInteractive source, + object source, IPointer pointer, - IVisual rootVisual, Point rootVisualPosition, + Visual rootVisual, Point rootVisualPosition, ulong timestamp, PointerPointProperties properties, KeyModifiers modifiers, @@ -144,8 +144,8 @@ namespace Avalonia.Input public class PointerReleasedEventArgs : PointerEventArgs { internal PointerReleasedEventArgs( - IInteractive source, IPointer pointer, - IVisual rootVisual, Point rootVisualPosition, ulong timestamp, + object source, IPointer pointer, + Visual rootVisual, Point rootVisualPosition, ulong timestamp, PointerPointProperties properties, KeyModifiers modifiers, MouseButton initialPressMouseButton) : base(InputElement.PointerReleasedEvent, source, pointer, rootVisual, rootVisualPosition, @@ -164,7 +164,7 @@ namespace Avalonia.Input { public IPointer Pointer { get; } - internal PointerCaptureLostEventArgs(IInteractive source, IPointer pointer) : base(InputElement.PointerCaptureLostEvent) + internal PointerCaptureLostEventArgs(object source, IPointer pointer) : base(InputElement.PointerCaptureLostEvent) { Pointer = pointer; Source = source; diff --git a/src/Avalonia.Base/Input/PointerOverPreProcessor.cs b/src/Avalonia.Base/Input/PointerOverPreProcessor.cs index e367f9f74f..7781255d89 100644 --- a/src/Avalonia.Base/Input/PointerOverPreProcessor.cs +++ b/src/Avalonia.Base/Input/PointerOverPreProcessor.cs @@ -44,7 +44,7 @@ namespace Avalonia.Input && _lastPointer is (var lastPointer, var lastPosition)) { _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()), args.InputModifiers.ToKeyModifiers()); } @@ -64,14 +64,14 @@ namespace Avalonia.Input { if (_lastPointer is (var pointer, var position)) { - var clientPoint = _inputRoot.PointToClient(position); + var clientPoint = PointToClient(_inputRoot, position); if (dirtyRect.Contains(clientPoint)) { var element = pointer.Captured ?? _inputRoot.InputHitTest(clientPoint); 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); } @@ -82,7 +82,7 @@ namespace Avalonia.Input { 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); } _lastPointer = null; @@ -101,10 +101,10 @@ namespace Avalonia.Input // Do not pass rootVisual, when we have unknown position, // so GetPosition won't return invalid values. 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); - if (element != null && !element.IsAttachedToVisualTree) + if (element is Visual v && !v.IsAttachedToVisualTree) { // element has been removed from visual tree so do top down cleanup if (root.IsPointerOver) @@ -117,7 +117,7 @@ namespace Avalonia.Input e.Source = element; e.Handled = false; element.RaiseEvent(e); - element = (IInputElement?)element.VisualParent; + element = GetVisualParent(element); } root.PointerOverElement = null; @@ -127,14 +127,18 @@ namespace Avalonia.Input 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); - break; + if (el.IsPointerOver) + { + ClearChildrenPointerOver(e, el, true); + break; + } } } + if (clearRoot) { 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, @@ -177,14 +181,14 @@ namespace Avalonia.Input branch = el; break; } - el = (IInputElement?)el.VisualParent; + el = GetVisualParent(el); } 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); - if (el != null && branch != null && !el.IsAttachedToVisualTree) + if (el is Visual v && branch != null && !v.IsAttachedToVisualTree) { ClearChildrenPointerOver(e, branch, false); } @@ -194,7 +198,7 @@ namespace Avalonia.Input e.Source = el; e.Handled = false; el.RaiseEvent(e); - el = (IInputElement?)el.VisualParent; + el = GetVisualParent(el); } el = root.PointerOverElement = element; @@ -206,8 +210,18 @@ namespace Avalonia.Input e.Source = el; e.Handled = false; 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); + } } } diff --git a/src/Avalonia.Base/Input/PointerWheelEventArgs.cs b/src/Avalonia.Base/Input/PointerWheelEventArgs.cs index 8a0412659b..a3de0eaaea 100644 --- a/src/Avalonia.Base/Input/PointerWheelEventArgs.cs +++ b/src/Avalonia.Base/Input/PointerWheelEventArgs.cs @@ -7,7 +7,7 @@ namespace Avalonia.Input { 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, PointerPointProperties properties, KeyModifiers modifiers, Vector delta) : base(InputElement.PointerWheelChangedEvent, source, pointer, rootVisual, rootVisualPosition, diff --git a/src/Avalonia.Base/Input/TappedEventArgs.cs b/src/Avalonia.Base/Input/TappedEventArgs.cs index 8af6164fc1..3e15c4843a 100644 --- a/src/Avalonia.Base/Input/TappedEventArgs.cs +++ b/src/Avalonia.Base/Input/TappedEventArgs.cs @@ -17,6 +17,6 @@ namespace Avalonia.Input public KeyModifiers KeyModifiers => lastPointerEventArgs.KeyModifiers; public ulong Timestamp => lastPointerEventArgs.Timestamp; - public Point GetPosition(IVisual? relativeTo) => lastPointerEventArgs.GetPosition(relativeTo); + public Point GetPosition(Visual? relativeTo) => lastPointerEventArgs.GetPosition(relativeTo); } } diff --git a/src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs b/src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs index 325d745782..4a60f8a046 100644 --- a/src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs +++ b/src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs @@ -16,7 +16,7 @@ namespace Avalonia.Input.TextInput /// /// The visual that's showing the text /// - IVisual TextViewVisual { get; } + Visual TextViewVisual { get; } /// /// Should be fired when text-hosting visual is changed /// diff --git a/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs b/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs index 4734224da4..8a3f5b968e 100644 --- a/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs +++ b/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs @@ -73,10 +73,13 @@ namespace Avalonia.Input.TextInput 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; - var transform = _focusedElement.TransformToVisual(_focusedElement.VisualRoot); + var transform = v.TransformToVisual(root); if (transform == null) _im.SetCursorRect(default); else @@ -95,7 +98,7 @@ namespace Avalonia.Input.TextInput return; _focusedElement = element; - var inputMethod = (element?.VisualRoot as ITextInputMethodRoot)?.InputMethod; + var inputMethod = ((element as Visual)?.VisualRoot as ITextInputMethodRoot)?.InputMethod; if (_im != inputMethod) { diff --git a/src/Avalonia.Base/Input/TextInput/TransformTrackingHelper.cs b/src/Avalonia.Base/Input/TextInput/TransformTrackingHelper.cs index 325c72bcb5..09716b4246 100644 --- a/src/Avalonia.Base/Input/TextInput/TransformTrackingHelper.cs +++ b/src/Avalonia.Base/Input/TextInput/TransformTrackingHelper.cs @@ -7,7 +7,7 @@ namespace Avalonia.Input.TextInput { class TransformTrackingHelper : IDisposable { - private IVisual? _visual; + private Visual? _visual; private bool _queuedForUpdate; private readonly EventHandler _propertyChangedHandler; private readonly List _propertyChangedSubscriptions = new List(); @@ -17,7 +17,7 @@ namespace Avalonia.Input.TextInput _propertyChangedHandler = PropertyChangedHandler; } - public void SetVisual(IVisual? visual) + public void SetVisual(Visual? visual) { Dispose(); _visual = visual; @@ -72,7 +72,7 @@ namespace Avalonia.Input.TextInput { Matrix? matrix = null; if (_visual != null && _visual.VisualRoot != null) - matrix = _visual.TransformToVisual(_visual.VisualRoot); + matrix = _visual.TransformToVisual((Visual)_visual.VisualRoot); if (Matrix != matrix) { Matrix = matrix; diff --git a/src/Avalonia.Base/Input/TouchDevice.cs b/src/Avalonia.Base/Input/TouchDevice.cs index c98944359d..3f0d221a68 100644 --- a/src/Avalonia.Base/Input/TouchDevice.cs +++ b/src/Avalonia.Base/Input/TouchDevice.cs @@ -75,7 +75,7 @@ namespace Avalonia.Input } 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), keyModifier, _clickCount)); } @@ -86,7 +86,7 @@ namespace Avalonia.Input using (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), keyModifier, MouseButton.Left)); } @@ -101,7 +101,7 @@ namespace Avalonia.Input 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, new PointerPointProperties(GetModifiers(args.InputModifiers, true), updateKind), keyModifier, args.IntermediatePoints)); diff --git a/src/Avalonia.Base/Interactivity/EventRoute.cs b/src/Avalonia.Base/Interactivity/EventRoute.cs index 8f826835da..3491465a68 100644 --- a/src/Avalonia.Base/Interactivity/EventRoute.cs +++ b/src/Avalonia.Base/Interactivity/EventRoute.cs @@ -43,7 +43,7 @@ namespace Avalonia.Interactivity /// `DynamicInvoke` on the handler. /// public void Add( - IInteractive target, + Interactive target, Delegate handler, RoutingStrategies routes, bool handledEventsToo = false, @@ -60,7 +60,7 @@ namespace Avalonia.Interactivity /// Adds a class handler to the route. /// /// The target on which the event should be raised. - public void AddClassHandler(IInteractive target) + public void AddClassHandler(Interactive target) { target = target ?? throw new ArgumentNullException(nameof(target)); @@ -73,7 +73,7 @@ namespace Avalonia.Interactivity /// /// The event source. /// The event args. - public void RaiseEvent(IInteractive source, RoutedEventArgs e) + public void RaiseEvent(Interactive source, RoutedEventArgs e) { source = source ?? throw new ArgumentNullException(nameof(source)); 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)); } - IInteractive? lastTarget = null; + Interactive? lastTarget = null; var start = 0; var end = _route.Count; var step = 1; @@ -177,7 +177,7 @@ namespace Avalonia.Interactivity private readonly struct RouteItem { public RouteItem( - IInteractive target, + Interactive target, Delegate? handler, Action? adapter, RoutingStrategies routes, @@ -190,7 +190,7 @@ namespace Avalonia.Interactivity HandledEventsToo = handledEventsToo; } - public IInteractive Target { get; } + public Interactive Target { get; } public Delegate? Handler { get; } public Action? Adapter { get; } public RoutingStrategies Routes { get; } diff --git a/src/Avalonia.Base/Interactivity/IInteractive.cs b/src/Avalonia.Base/Interactivity/IInteractive.cs deleted file mode 100644 index 980bf54f1f..0000000000 --- a/src/Avalonia.Base/Interactivity/IInteractive.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using Avalonia.Metadata; - -#nullable enable - -namespace Avalonia.Interactivity -{ - /// - /// Interface for objects that raise routed events. - /// - [NotClientImplementable] - public interface IInteractive - { - /// - /// Gets the interactive parent of the object for bubbling and tunneling events. - /// - IInteractive? InteractiveParent { get; } - - /// - /// Adds a handler for the specified routed event. - /// - /// The routed event. - /// The handler. - /// The routing strategies to listen to. - /// Whether handled events should also be listened for. - /// A disposable that terminates the event subscription. - void AddHandler( - RoutedEvent routedEvent, - Delegate handler, - RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble, - bool handledEventsToo = false); - - /// - /// Removes a handler for the specified routed event. - /// - /// The routed event. - /// The handler. - void RemoveHandler(RoutedEvent routedEvent, Delegate handler); - - /// - /// Adds the object's handlers for a routed event to an event route. - /// - /// The event. - /// The event route. - void AddToEventRoute(RoutedEvent routedEvent, EventRoute route); - - /// - /// Raises a routed event. - /// - /// The event args. - void RaiseEvent(RoutedEventArgs e); - } -} diff --git a/src/Avalonia.Base/Interactivity/Interactive.cs b/src/Avalonia.Base/Interactivity/Interactive.cs index 75f74ed3b3..821e00d784 100644 --- a/src/Avalonia.Base/Interactivity/Interactive.cs +++ b/src/Avalonia.Base/Interactivity/Interactive.cs @@ -10,14 +10,14 @@ namespace Avalonia.Interactivity /// /// Base class for objects that raise routed events. /// - public class Interactive : Layoutable, IInteractive + public class Interactive : Layoutable { private Dictionary>? _eventHandlers; /// /// Gets the interactive parent of the object for bubbling and tunneling events. /// - IInteractive? IInteractive.InteractiveParent => ((IVisual)this).VisualParent as IInteractive; + protected internal virtual Interactive? InteractiveParent => VisualParent as Interactive; /// /// Adds a handler for the specified routed event. @@ -125,21 +125,6 @@ namespace Avalonia.Interactivity 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); - } - } - } - /// /// Builds an event route for a routed event. /// @@ -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 /// this method to build the event route and check the /// property to see if there are any handlers registered on the route. If there are, call - /// to raise the event. + /// to raise the event. /// protected EventRoute BuildEventRoute(RoutedEvent e) { @@ -163,7 +148,7 @@ namespace Avalonia.Interactivity if (e.RoutingStrategies.HasAllFlags(RoutingStrategies.Bubble) || e.RoutingStrategies.HasAllFlags(RoutingStrategies.Tunnel)) { - IInteractive? element = this; + Interactive? element = this; while (element != null) { @@ -183,7 +168,7 @@ namespace Avalonia.Interactivity result.AddClassHandler(this); } - ((IInteractive)this).AddToEventRoute(e, result); + AddToEventRoute(e, result); } return result; @@ -202,6 +187,21 @@ namespace Avalonia.Interactivity 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 { public EventSubscription( diff --git a/src/Avalonia.Base/Interactivity/InteractiveExtensions.cs b/src/Avalonia.Base/Interactivity/InteractiveExtensions.cs index ee6b623b5f..b12cf64fdf 100644 --- a/src/Avalonia.Base/Interactivity/InteractiveExtensions.cs +++ b/src/Avalonia.Base/Interactivity/InteractiveExtensions.cs @@ -5,7 +5,7 @@ using System.Reactive.Linq; namespace Avalonia.Interactivity { /// - /// Provides extension methods for the interface. + /// Provides extension methods for the interface. /// public static class InteractiveExtensions { @@ -19,7 +19,7 @@ namespace Avalonia.Interactivity /// The routing strategies to listen to. /// Whether handled events should also be listened for. /// A disposable that terminates the event subscription. - public static IDisposable AddDisposableHandler(this IInteractive o, RoutedEvent routedEvent, + public static IDisposable AddDisposableHandler(this Interactive o, RoutedEvent routedEvent, EventHandler handler, RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble, bool handledEventsToo = false) where TEventArgs : RoutedEventArgs @@ -31,6 +31,8 @@ namespace Avalonia.Interactivity state => state.instance.RemoveHandler(state.routedEvent, state.handler)); } + public static Interactive? GetInteractiveParent(this Interactive o) => o.InteractiveParent; + /// /// Gets an observable for a . /// @@ -42,7 +44,7 @@ namespace Avalonia.Interactivity /// An observable which fires each time the event is raised. /// public static IObservable GetObservable( - this IInteractive o, + this Interactive o, RoutedEvent routedEvent, RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble, bool handledEventsToo = false) diff --git a/src/Avalonia.Base/Interactivity/RoutedEvent.cs b/src/Avalonia.Base/Interactivity/RoutedEvent.cs index 37edb24cc1..7046871538 100644 --- a/src/Avalonia.Base/Interactivity/RoutedEvent.cs +++ b/src/Avalonia.Base/Interactivity/RoutedEvent.cs @@ -116,7 +116,7 @@ namespace Avalonia.Interactivity public IDisposable AddClassHandler( Action handler, 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) { diff --git a/src/Avalonia.Base/Interactivity/RoutedEventArgs.cs b/src/Avalonia.Base/Interactivity/RoutedEventArgs.cs index 2b660e7080..17164bd333 100644 --- a/src/Avalonia.Base/Interactivity/RoutedEventArgs.cs +++ b/src/Avalonia.Base/Interactivity/RoutedEventArgs.cs @@ -28,7 +28,7 @@ namespace Avalonia.Interactivity /// /// The routed event associated with these event args. /// The source object that raised the routed event. - public RoutedEventArgs(RoutedEvent? routedEvent, IInteractive? source) + public RoutedEventArgs(RoutedEvent? routedEvent, object? source) { RoutedEvent = routedEvent; Source = source; @@ -55,6 +55,6 @@ namespace Avalonia.Interactivity /// /// Gets or sets the source object that raised the routed event. /// - public IInteractive? Source { get; set; } + public object? Source { get; set; } } } diff --git a/src/Avalonia.Base/Layout/AttachedLayout.cs b/src/Avalonia.Base/Layout/AttachedLayout.cs index ece8bbe805..950ea34319 100644 --- a/src/Avalonia.Base/Layout/AttachedLayout.cs +++ b/src/Avalonia.Base/Layout/AttachedLayout.cs @@ -43,7 +43,7 @@ namespace Avalonia.Layout /// /// Initializes any per-container state the layout requires when it is attached to an - /// container. + /// container. /// /// /// The context object that facilitates communication between the layout and its host diff --git a/src/Avalonia.Base/Layout/ElementManager.cs b/src/Avalonia.Base/Layout/ElementManager.cs index 048e8eebee..73d29510bd 100644 --- a/src/Avalonia.Base/Layout/ElementManager.cs +++ b/src/Avalonia.Base/Layout/ElementManager.cs @@ -13,7 +13,7 @@ namespace Avalonia.Layout { internal class ElementManager { - private readonly List _realizedElements = new List(); + private readonly List _realizedElements = new List(); private readonly List _realizedElementLayoutBounds = new List(); private int _firstRealizedDataIndex; private VirtualizingLayoutContext? _context; @@ -69,9 +69,9 @@ namespace Avalonia.Layout return IsVirtualizingContext ? _realizedElements.Count : _context!.ItemCount; } - public ILayoutable GetAt(int realizedIndex) + public Layoutable GetAt(int realizedIndex) { - ILayoutable? element; + Layoutable? element; if (IsVirtualizingContext) { @@ -99,7 +99,7 @@ namespace Avalonia.Layout return element; } - public void Add(ILayoutable element, int dataIndex) + public void Add(Layoutable element, int dataIndex) { if (_realizedElements.Count == 0) { @@ -110,7 +110,7 @@ namespace Avalonia.Layout _realizedElementLayoutBounds.Add(default); } - public void Insert(int realizedIndex, int dataIndex, ILayoutable? element) + public void Insert(int realizedIndex, int dataIndex, Layoutable? element) { if (realizedIndex == 0) { @@ -207,7 +207,7 @@ namespace Avalonia.Layout public bool IsIndexValidInData(int currentIndex) => (uint)currentIndex < _context!.ItemCount; - public ILayoutable? GetRealizedElement(int dataIndex) + public Layoutable? GetRealizedElement(int dataIndex) { return IsVirtualizingContext ? 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); return it != -1 ? GetDataIndexFromRealizedRangeIndex(it) : -1; diff --git a/src/Avalonia.Base/Layout/FlowLayoutAlgorithm.cs b/src/Avalonia.Base/Layout/FlowLayoutAlgorithm.cs index e6a95d1ffb..c46c4ca98c 100644 --- a/src/Avalonia.Base/Layout/FlowLayoutAlgorithm.cs +++ b/src/Avalonia.Base/Layout/FlowLayoutAlgorithm.cs @@ -144,7 +144,7 @@ namespace Avalonia.Layout } public Size MeasureElement( - ILayoutable element, + Layoutable element, int index, Size availableSize, VirtualizingLayoutContext context) @@ -512,9 +512,9 @@ namespace Avalonia.Layout private Rect EstimateExtent(Size availableSize, string? layoutId) { - ILayoutable? firstRealizedElement = null; + Layoutable? firstRealizedElement = null; Rect firstBounds = new Rect(); - ILayoutable? lastRealizedElement = null; + Layoutable? lastRealizedElement = null; Rect lastBounds = new Rect(); int firstDataIndex = -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)) { @@ -737,7 +737,7 @@ namespace Avalonia.Layout return null; } - public bool TryAddElement0(ILayoutable element) + public bool TryAddElement0(Layoutable element) { if (_elementManager.GetRealizedElementCount() == 0) { diff --git a/src/Avalonia.Base/Layout/IFlowLayoutAlgorithmDelegates.cs b/src/Avalonia.Base/Layout/IFlowLayoutAlgorithmDelegates.cs index e3eae0caf7..cb1601799e 100644 --- a/src/Avalonia.Base/Layout/IFlowLayoutAlgorithmDelegates.cs +++ b/src/Avalonia.Base/Layout/IFlowLayoutAlgorithmDelegates.cs @@ -21,14 +21,14 @@ namespace Avalonia.Layout Rect Algorithm_GetExtent( Size availableSize, VirtualizingLayoutContext context, - ILayoutable? firstRealized, + Layoutable? firstRealized, int firstRealizedItemIndex, Rect firstRealizedLayoutBounds, - ILayoutable? lastRealized, + Layoutable? lastRealized, int lastRealizedItemIndex, Rect lastRealizedLayoutBounds); void Algorithm_OnElementMeasured( - ILayoutable element, + Layoutable element, int index, Size availableSize, Size measureSize, diff --git a/src/Avalonia.Base/Layout/ILayoutManager.cs b/src/Avalonia.Base/Layout/ILayoutManager.cs index 88d95c81fd..38d46557e1 100644 --- a/src/Avalonia.Base/Layout/ILayoutManager.cs +++ b/src/Avalonia.Base/Layout/ILayoutManager.cs @@ -18,13 +18,13 @@ namespace Avalonia.Layout /// Notifies the layout manager that a control requires a measure. /// /// The control. - void InvalidateMeasure(ILayoutable control); + void InvalidateMeasure(Layoutable control); /// /// Notifies the layout manager that a control requires an arrange. /// /// The control. - void InvalidateArrange(ILayoutable control); + void InvalidateArrange(Layoutable control); /// /// Executes a layout pass. @@ -48,12 +48,12 @@ namespace Avalonia.Layout /// Registers a control as wanting to receive effective viewport notifications. /// /// The control. - void RegisterEffectiveViewportListener(ILayoutable control); + void RegisterEffectiveViewportListener(Layoutable control); /// /// Registers a control as no longer wanting to receive effective viewport notifications. /// /// The control. - void UnregisterEffectiveViewportListener(ILayoutable control); + void UnregisterEffectiveViewportListener(Layoutable control); } } diff --git a/src/Avalonia.Base/Layout/ILayoutRoot.cs b/src/Avalonia.Base/Layout/ILayoutRoot.cs index df15fc1a1d..c90ddb9219 100644 --- a/src/Avalonia.Base/Layout/ILayoutRoot.cs +++ b/src/Avalonia.Base/Layout/ILayoutRoot.cs @@ -6,7 +6,7 @@ namespace Avalonia.Layout /// Defines the root of a layoutable tree. /// [NotClientImplementable] - public interface ILayoutRoot : ILayoutable + public interface ILayoutRoot { /// /// The size available to lay out the controls. diff --git a/src/Avalonia.Base/Layout/ILayoutable.cs b/src/Avalonia.Base/Layout/ILayoutable.cs deleted file mode 100644 index d8b546b04a..0000000000 --- a/src/Avalonia.Base/Layout/ILayoutable.cs +++ /dev/null @@ -1,122 +0,0 @@ -using Avalonia.Metadata; -using Avalonia.VisualTree; - -namespace Avalonia.Layout -{ - /// - /// Defines layout-related functionality for a control. - /// - [NotClientImplementable] - public interface ILayoutable : IVisual - { - /// - /// Gets the size that this element computed during the measure pass of the layout process. - /// - Size DesiredSize { get; } - - /// - /// Gets the width of the element. - /// - double Width { get; } - - /// - /// Gets the height of the element. - /// - double Height { get; } - - /// - /// Gets the minimum width of the element. - /// - double MinWidth { get; } - - /// - /// Gets the maximum width of the element. - /// - double MaxWidth { get; } - - /// - /// Gets the minimum height of the element. - /// - double MinHeight { get; } - - /// - /// Gets the maximum height of the element. - /// - double MaxHeight { get; } - - /// - /// Gets the margin around the element. - /// - Thickness Margin { get; } - - /// - /// Gets the element's preferred horizontal alignment in its parent. - /// - HorizontalAlignment HorizontalAlignment { get; } - - /// - /// Gets the element's preferred vertical alignment in its parent. - /// - VerticalAlignment VerticalAlignment { get; } - - /// - /// Gets a value indicating whether the control's layout measure is valid. - /// - bool IsMeasureValid { get; } - - /// - /// Gets a value indicating whether the control's layouts arrange is valid. - /// - bool IsArrangeValid { get; } - - /// - /// Gets the available size passed in the previous layout pass, if any. - /// - Size? PreviousMeasure { get; } - - /// - /// Gets the layout rect passed in the previous layout pass, if any. - /// - Rect? PreviousArrange { get; } - - /// - /// Creates the visual children of the control, if necessary - /// - void ApplyTemplate(); - - /// - /// Carries out a measure of the control. - /// - /// The available size for the control. - void Measure(Size availableSize); - - /// - /// Arranges the control and its children. - /// - /// The control's new bounds. - void Arrange(Rect rect); - - /// - /// Invalidates the measurement of the control and queues a new layout pass. - /// - void InvalidateMeasure(); - - /// - /// Invalidates the arrangement of the control and queues a new layout pass. - /// - void InvalidateArrange(); - - /// - /// Called when a child control's desired size changes. - /// - /// The child control. - void ChildDesiredSizeChanged(ILayoutable control); - - /// - /// Used by the to notify the control that its effective - /// viewport is changed. - /// - /// The viewport information. - void EffectiveViewportChanged(EffectiveViewportChangedEventArgs e); - } -} diff --git a/src/Avalonia.Base/Layout/LayoutContextAdapter.cs b/src/Avalonia.Base/Layout/LayoutContextAdapter.cs index 25132c2802..e347e2be8b 100644 --- a/src/Avalonia.Base/Layout/LayoutContextAdapter.cs +++ b/src/Avalonia.Base/Layout/LayoutContextAdapter.cs @@ -38,8 +38,8 @@ namespace Avalonia.Layout protected override int ItemCountCore() => _nonVirtualizingContext.Children.Count; 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]; - protected override void RecycleElementCore(ILayoutable element) { } + protected override void RecycleElementCore(Layoutable element) { } } } diff --git a/src/Avalonia.Base/Layout/LayoutHelper.cs b/src/Avalonia.Base/Layout/LayoutHelper.cs index 0851dbaea9..c6cbda6255 100644 --- a/src/Avalonia.Base/Layout/LayoutHelper.cs +++ b/src/Avalonia.Base/Layout/LayoutHelper.cs @@ -16,15 +16,15 @@ namespace Avalonia.Layout public static double LayoutEpsilon { get; } = 0.00000153; /// - /// Calculates a control's size based on its , - /// , , - /// , and - /// . + /// Calculates a control's size based on its , + /// , , + /// , and + /// . /// /// The control. /// The space available for the control. /// The control's size. - public static Size ApplyLayoutConstraints(ILayoutable control, Size constraints) + public static Size ApplyLayoutConstraints(Layoutable control, Size constraints) { var minmax = new MinMax(control); @@ -33,7 +33,7 @@ namespace Avalonia.Layout 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) { if (IsParentLayoutRounded(control, out double scale)) @@ -51,7 +51,7 @@ namespace Avalonia.Layout 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)) { @@ -67,7 +67,7 @@ namespace Avalonia.Layout 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)) { @@ -78,7 +78,7 @@ namespace Avalonia.Layout 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)) padding = RoundLayoutThickness(padding, scale, scale); @@ -86,18 +86,18 @@ namespace Avalonia.Layout 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)); 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; return false; @@ -110,11 +110,11 @@ namespace Avalonia.Layout /// /// Invalidates measure for given control and all visual children recursively. /// - 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(); } @@ -124,13 +124,14 @@ namespace Avalonia.Layout for (int i = 0; i < visualChildrenCount; i++) { - IVisual child = visualChildren[i]; + Visual child = visualChildren[i]; InnerInvalidateMeasure(child); } } - InnerInvalidateMeasure(control); + if (control is Visual v) + InnerInvalidateMeasure(v); } /// @@ -138,9 +139,9 @@ namespace Avalonia.Layout /// /// The control. /// Thrown when control has no root or returned layout scaling is invalid. - 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; @@ -289,7 +290,7 @@ namespace Avalonia.Layout /// private readonly struct MinMax { - public MinMax(ILayoutable e) + public MinMax(Layoutable e) { MaxHeight = e.MaxHeight; MinHeight = e.MinHeight; diff --git a/src/Avalonia.Base/Layout/LayoutManager.cs b/src/Avalonia.Base/Layout/LayoutManager.cs index 826947c39a..242d70821a 100644 --- a/src/Avalonia.Base/Layout/LayoutManager.cs +++ b/src/Avalonia.Base/Layout/LayoutManager.cs @@ -16,9 +16,9 @@ namespace Avalonia.Layout public class LayoutManager : ILayoutManager, IDisposable { private const int MaxPasses = 3; - private readonly ILayoutRoot _owner; - private readonly LayoutQueue _toMeasure = new LayoutQueue(v => !v.IsMeasureValid); - private readonly LayoutQueue _toArrange = new LayoutQueue(v => !v.IsArrangeValid); + private readonly Layoutable _owner; + private readonly LayoutQueue _toMeasure = new LayoutQueue(v => !v.IsMeasureValid); + private readonly LayoutQueue _toArrange = new LayoutQueue(v => !v.IsArrangeValid); private readonly Action _executeLayoutPass; private List? _effectiveViewportChangedListeners; private bool _disposed; @@ -27,14 +27,14 @@ namespace Avalonia.Layout public LayoutManager(ILayoutRoot owner) { - _owner = owner ?? throw new ArgumentNullException(nameof(owner)); + _owner = owner as Layoutable ?? throw new ArgumentNullException(nameof(owner)); _executeLayoutPass = ExecuteQueuedLayoutPass; } public virtual event EventHandler? LayoutUpdated; /// - public virtual void InvalidateMeasure(ILayoutable control) + public virtual void InvalidateMeasure(Layoutable control) { control = control ?? throw new ArgumentNullException(nameof(control)); Dispatcher.UIThread.VerifyAccess(); @@ -65,7 +65,7 @@ namespace Avalonia.Layout } /// - public virtual void InvalidateArrange(ILayoutable control) + public virtual void InvalidateArrange(Layoutable control) { control = control ?? throw new ArgumentNullException(nameof(control)); Dispatcher.UIThread.VerifyAccess(); @@ -203,7 +203,7 @@ namespace Avalonia.Layout _toArrange.Dispose(); } - void ILayoutManager.RegisterEffectiveViewportListener(ILayoutable control) + void ILayoutManager.RegisterEffectiveViewportListener(Layoutable control) { _effectiveViewportChangedListeners ??= new List(); _effectiveViewportChangedListeners.Add(new EffectiveViewportChangedListener( @@ -211,7 +211,7 @@ namespace Avalonia.Layout CalculateEffectiveViewport(control))); } - void ILayoutManager.UnregisterEffectiveViewportListener(ILayoutable control) + void ILayoutManager.UnregisterEffectiveViewportListener(Layoutable control) { 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 // 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 // fastest and most memory-efficient algorithm. - if (control.VisualParent is ILayoutable parent) + if (control.VisualParent is Layoutable parent) { Measure(parent); } @@ -283,7 +283,7 @@ namespace Avalonia.Layout { if (control is ILayoutRoot root) { - root.Measure(Size.Infinity); + control.Measure(Size.Infinity); } 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); } @@ -304,7 +304,7 @@ namespace Avalonia.Layout if (control is IEmbeddedLayoutRoot embeddedRoot) control.Arrange(new Rect(embeddedRoot.AllocatedSize)); else if (control is ILayoutRoot root) - control.Arrange(new Rect(root.DesiredSize)); + control.Arrange(new Rect(control.DesiredSize)); else if (control.PreviousArrange != null) { // Has been observed that PreviousArrange sometimes is null, probably a bug somewhere else. @@ -350,7 +350,7 @@ namespace Avalonia.Layout if (viewport != l.Viewport) { - l.Listener.EffectiveViewportChanged(new EffectiveViewportChangedEventArgs(viewport)); + l.Listener.RaiseEffectiveViewportChanged(new EffectiveViewportChangedEventArgs(viewport)); l.Viewport = viewport; } } @@ -364,14 +364,14 @@ namespace Avalonia.Layout 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); CalculateEffectiveViewport(control, control, ref 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. if (control.VisualParent is object) @@ -405,13 +405,13 @@ namespace Avalonia.Layout private class EffectiveViewportChangedListener { - public EffectiveViewportChangedListener(ILayoutable listener, Rect viewport) + public EffectiveViewportChangedListener(Layoutable listener, Rect viewport) { Listener = listener; Viewport = viewport; } - public ILayoutable Listener { get; } + public Layoutable Listener { get; } public Rect Viewport { get; set; } } } diff --git a/src/Avalonia.Base/Layout/Layoutable.cs b/src/Avalonia.Base/Layout/Layoutable.cs index 527b63292d..d185148894 100644 --- a/src/Avalonia.Base/Layout/Layoutable.cs +++ b/src/Avalonia.Base/Layout/Layoutable.cs @@ -1,5 +1,6 @@ using System; using Avalonia.Logging; +using Avalonia.Styling; using Avalonia.VisualTree; #nullable enable @@ -61,7 +62,7 @@ namespace Avalonia.Layout /// /// Implements layout-related functionality for a control. /// - public class Layoutable : Visual, ILayoutable + public class Layoutable : Visual { /// /// Defines the property. @@ -322,15 +323,12 @@ namespace Avalonia.Layout set { SetValue(UseLayoutRoundingProperty, value); } } - /// - /// Gets the available size passed in the previous layout pass, if any. - /// - Size? ILayoutable.PreviousMeasure => _previousMeasure; + internal Size? PreviousMeasure => _previousMeasure; /// /// Gets the layout rect passed in the previous layout pass, if any. /// - Rect? ILayoutable.PreviousArrange => _previousArrange; + internal Rect? PreviousArrange => _previousArrange; /// /// Creates the visual children of the control, if necessary @@ -379,7 +377,7 @@ namespace Avalonia.Layout if (DesiredSize != previousDesiredSize) { - this.GetVisualParent()?.ChildDesiredSizeChanged(this); + this.GetVisualParent()?.ChildDesiredSizeChanged(this); } } } @@ -422,7 +420,7 @@ namespace Avalonia.Layout IsMeasureValid = false; IsArrangeValid = false; - if (((ILayoutable)this).IsAttachedToVisualTree) + if (IsAttachedToVisualTree) { (VisualRoot as ILayoutRoot)?.LayoutManager.InvalidateMeasure(this); InvalidateVisual(); @@ -447,7 +445,7 @@ namespace Avalonia.Layout } /// - void ILayoutable.ChildDesiredSizeChanged(ILayoutable control) + internal void ChildDesiredSizeChanged(Layoutable control) { if (!_measuring) { @@ -455,11 +453,11 @@ namespace Avalonia.Layout } } - void ILayoutable.EffectiveViewportChanged(EffectiveViewportChangedEventArgs e) + internal void RaiseEffectiveViewportChanged(EffectiveViewportChangedEventArgs e) { _effectiveViewportChanged?.Invoke(this, e); } - + /// /// Marks a property as affecting the control's measurement. /// @@ -470,7 +468,7 @@ namespace Avalonia.Layout /// property will cause to be called on the element. /// protected static void AffectsMeasure(params AvaloniaProperty[] properties) - where T : class, ILayoutable + where T : Layoutable { void Invalidate(AvaloniaPropertyChangedEventArgs e) { @@ -493,7 +491,7 @@ namespace Avalonia.Layout /// property will cause to be called on the element. /// protected static void AffectsArrange(params AvaloniaProperty[] properties) - where T : class, ILayoutable + where T : Layoutable { void Invalidate(AvaloniaPropertyChangedEventArgs e) { @@ -595,9 +593,9 @@ namespace Avalonia.Layout 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); width = Math.Max(width, layoutable.DesiredSize.Width); @@ -708,9 +706,9 @@ namespace Avalonia.Layout 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); } @@ -719,9 +717,9 @@ namespace Avalonia.Layout return finalSize; } - protected sealed override void InvalidateStyles() + internal sealed override void InvalidateStyles(bool recurse) { - base.InvalidateStyles(); + base.InvalidateStyles(recurse); InvalidateMeasure(); } @@ -777,7 +775,7 @@ namespace Avalonia.Layout DesiredSize = default; // All changes to visibility cause the parent element to be notified. - this.GetVisualParent()?.ChildDesiredSizeChanged(this); + this.GetVisualParent()?.ChildDesiredSizeChanged(this); // We only invalidate outselves when visibility is changed to true. if (change.GetNewValue()) @@ -788,13 +786,19 @@ namespace Avalonia.Layout } /// - protected sealed override void OnVisualParentChanged(IVisual? oldParent, IVisual? newParent) + protected sealed override void OnVisualParentChanged(Visual? oldParent, Visual? newParent) { LayoutHelper.InvalidateSelfAndChildrenMeasure(this); base.OnVisualParentChanged(oldParent, newParent); } + private protected override void OnControlThemeChanged() + { + base.OnControlThemeChanged(); + InvalidateMeasure(); + } + /// /// Called when the layout manager raises a LayoutUpdated event. /// diff --git a/src/Avalonia.Base/Layout/NonVirtualizingLayoutContext.cs b/src/Avalonia.Base/Layout/NonVirtualizingLayoutContext.cs index 91348f5e6d..78ce5c480e 100644 --- a/src/Avalonia.Base/Layout/NonVirtualizingLayoutContext.cs +++ b/src/Avalonia.Base/Layout/NonVirtualizingLayoutContext.cs @@ -17,13 +17,13 @@ namespace Avalonia.Layout /// /// Gets the collection of child controls from the container that provides the context. /// - public IReadOnlyList Children => ChildrenCore; + public IReadOnlyList Children => ChildrenCore; /// /// Implements the behavior for getting the return value of in a /// derived or custom . /// - protected abstract IReadOnlyList ChildrenCore { get; } + protected abstract IReadOnlyList ChildrenCore { get; } internal VirtualizingLayoutContext GetVirtualizingContextAdapter() => _contextAdapter ??= new LayoutContextAdapter(this); diff --git a/src/Avalonia.Base/Layout/NonVirtualizingStackLayout.cs b/src/Avalonia.Base/Layout/NonVirtualizingStackLayout.cs index 6fa8601916..72ff1780ab 100644 --- a/src/Avalonia.Base/Layout/NonVirtualizingStackLayout.cs +++ b/src/Avalonia.Base/Layout/NonVirtualizingStackLayout.cs @@ -59,7 +59,7 @@ namespace Avalonia.Layout { var element = context.Children[i]; - if (!element.IsVisible) + if ((element as Visual)?.IsVisible == false) { continue; } @@ -100,7 +100,7 @@ namespace Avalonia.Layout { var element = context.Children[i]; - if (!element.IsVisible) + if ((element as Visual)?.IsVisible == false) { continue; } @@ -117,7 +117,7 @@ namespace Avalonia.Layout 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 width = element.DesiredSize.Width; @@ -138,7 +138,7 @@ namespace Avalonia.Layout 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 height = element.DesiredSize.Height; diff --git a/src/Avalonia.Base/Layout/StackLayout.cs b/src/Avalonia.Base/Layout/StackLayout.cs index e3c2ab3817..5c1797a7a4 100644 --- a/src/Avalonia.Base/Layout/StackLayout.cs +++ b/src/Avalonia.Base/Layout/StackLayout.cs @@ -78,10 +78,10 @@ namespace Avalonia.Layout internal Rect GetExtent( Size availableSize, VirtualizingLayoutContext context, - ILayoutable? firstRealized, + Layoutable? firstRealized, int firstRealizedItemIndex, Rect firstRealizedLayoutBounds, - ILayoutable? lastRealized, + Layoutable? lastRealized, int lastRealizedItemIndex, Rect lastRealizedLayoutBounds) { @@ -121,7 +121,7 @@ namespace Avalonia.Layout } internal void OnElementMeasured( - ILayoutable element, + Layoutable element, int index, Size availableSize, Size measureSize, @@ -188,10 +188,10 @@ namespace Avalonia.Layout Rect IFlowLayoutAlgorithmDelegates.Algorithm_GetExtent( Size availableSize, VirtualizingLayoutContext context, - ILayoutable? firstRealized, + Layoutable? firstRealized, int firstRealizedItemIndex, Rect firstRealizedLayoutBounds, - ILayoutable? lastRealized, + Layoutable? lastRealized, int lastRealizedItemIndex, Rect lastRealizedLayoutBounds) { @@ -206,7 +206,7 @@ namespace Avalonia.Layout 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( element, diff --git a/src/Avalonia.Base/Layout/UniformGridLayout.cs b/src/Avalonia.Base/Layout/UniformGridLayout.cs index a7880a1545..225a491b60 100644 --- a/src/Avalonia.Base/Layout/UniformGridLayout.cs +++ b/src/Avalonia.Base/Layout/UniformGridLayout.cs @@ -338,10 +338,10 @@ namespace Avalonia.Layout Rect IFlowLayoutAlgorithmDelegates.Algorithm_GetExtent( Size availableSize, VirtualizingLayoutContext context, - ILayoutable? firstRealized, + Layoutable? firstRealized, int firstRealizedItemIndex, Rect firstRealizedLayoutBounds, - ILayoutable? lastRealized, + Layoutable? lastRealized, int lastRealizedItemIndex, Rect lastRealizedLayoutBounds) { @@ -391,7 +391,7 @@ namespace Avalonia.Layout 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) { } diff --git a/src/Avalonia.Base/Layout/UniformGridLayoutState.cs b/src/Avalonia.Base/Layout/UniformGridLayoutState.cs index 65cdf4c94b..fe2fe474da 100644 --- a/src/Avalonia.Base/Layout/UniformGridLayoutState.cs +++ b/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 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. - private ILayoutable? _cachedFirstElement; + private Layoutable? _cachedFirstElement; internal FlowLayoutAlgorithm FlowAlgorithm { get; } = new FlowLayoutAlgorithm(); internal double EffectiveItemWidth { get; private set; } @@ -91,7 +91,7 @@ namespace Avalonia.Layout } private void SetSize( - ILayoutable element, + Layoutable element, double layoutItemWidth, double layoutItemHeight, Size availableSize, diff --git a/src/Avalonia.Base/Layout/VirtualLayoutContextAdapter.cs b/src/Avalonia.Base/Layout/VirtualLayoutContextAdapter.cs index f068187523..cc8cca5498 100644 --- a/src/Avalonia.Base/Layout/VirtualLayoutContextAdapter.cs +++ b/src/Avalonia.Base/Layout/VirtualLayoutContextAdapter.cs @@ -19,18 +19,18 @@ namespace Avalonia.Layout set => _virtualizingContext.LayoutState = value; } - protected override IReadOnlyList ChildrenCore => + protected override IReadOnlyList ChildrenCore => _children ?? (_children = new ChildrenCollection(_virtualizingContext)); - private class ChildrenCollection : IReadOnlyList + private class ChildrenCollection : IReadOnlyList { private readonly VirtualizingLayoutContext _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; IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - public IEnumerator GetEnumerator() + public IEnumerator GetEnumerator() { for (var i = 0; i < Count; ++i) { diff --git a/src/Avalonia.Base/Layout/VirtualizingLayoutContext.cs b/src/Avalonia.Base/Layout/VirtualizingLayoutContext.cs index f48c9b0ef2..7fbef19d16 100644 --- a/src/Avalonia.Base/Layout/VirtualizingLayoutContext.cs +++ b/src/Avalonia.Base/Layout/VirtualizingLayoutContext.cs @@ -116,7 +116,7 @@ namespace Avalonia.Layout /// This method calls /// with options set to None. GetElementAtCore must be implemented in a derived class. /// - public ILayoutable GetOrCreateElementAt(int index) + public Layoutable GetOrCreateElementAt(int index) => GetOrCreateElementAtCore(index, ElementRealizationOptions.None); /// @@ -140,7 +140,7 @@ namespace Avalonia.Layout /// advanced layouts that choose to explicitly manage the realization and recycling of /// elements as a performance optimization. /// - public ILayoutable GetOrCreateElementAt(int index, ElementRealizationOptions options) + public Layoutable GetOrCreateElementAt(int index, ElementRealizationOptions options) => GetOrCreateElementAtCore(index, options); /// @@ -148,10 +148,10 @@ namespace Avalonia.Layout /// /// The element to clear. /// - /// This method calls , which must be implemented + /// This method calls , which must be implemented /// in a derived class. /// - public void RecycleElement(ILayoutable element) => RecycleElementCore(element); + public void RecycleElement(Layoutable element) => RecycleElementCore(element); /// /// When implemented in a derived class, retrieves the number of items in the data. @@ -180,14 +180,14 @@ namespace Avalonia.Layout /// A value of that specifies whether to suppress /// automatic recycling of the retrieved element or force creation of a new element. /// - protected abstract ILayoutable GetOrCreateElementAtCore(int index, ElementRealizationOptions options); + protected abstract Layoutable GetOrCreateElementAtCore(int index, ElementRealizationOptions options); /// /// When implemented in a derived class, clears the specified UIElement and allows it to be /// either re-used or released. /// /// The element to clear. - protected abstract void RecycleElementCore(ILayoutable element); + protected abstract void RecycleElementCore(Layoutable element); internal NonVirtualizingLayoutContext GetNonVirtualizingContextAdapter() => _contextAdapter ?? (_contextAdapter = new VirtualLayoutContextAdapter(this)); diff --git a/src/Avalonia.Base/Layout/WrapLayout/WrapItem.cs b/src/Avalonia.Base/Layout/WrapLayout/WrapItem.cs index e823ade6d4..7a44f034f5 100644 --- a/src/Avalonia.Base/Layout/WrapLayout/WrapItem.cs +++ b/src/Avalonia.Base/Layout/WrapLayout/WrapItem.cs @@ -20,6 +20,6 @@ namespace Avalonia.Layout public UvMeasure? Position { get; internal set; } - public ILayoutable? Element { get; internal set; } + public Layoutable? Element { get; internal set; } } } diff --git a/src/Avalonia.Base/Media/IVisualBrush.cs b/src/Avalonia.Base/Media/IVisualBrush.cs index b8900bbd73..6662613ff4 100644 --- a/src/Avalonia.Base/Media/IVisualBrush.cs +++ b/src/Avalonia.Base/Media/IVisualBrush.cs @@ -4,7 +4,7 @@ using Avalonia.VisualTree; namespace Avalonia.Media { /// - /// Paints an area with an . + /// Paints an area with an . /// [NotClientImplementable] public interface IVisualBrush : ITileBrush @@ -12,6 +12,6 @@ namespace Avalonia.Media /// /// Gets the visual to draw. /// - IVisual Visual { get; } + Visual Visual { get; } } } diff --git a/src/Avalonia.Base/Media/Imaging/RenderTargetBitmap.cs b/src/Avalonia.Base/Media/Imaging/RenderTargetBitmap.cs index 94a5abd918..c4508c3f5c 100644 --- a/src/Avalonia.Base/Media/Imaging/RenderTargetBitmap.cs +++ b/src/Avalonia.Base/Media/Imaging/RenderTargetBitmap.cs @@ -44,7 +44,7 @@ namespace Avalonia.Media.Imaging /// Renders a visual to the . /// /// The visual to render. - public void Render(IVisual visual) => ImmediateRenderer.Render(visual, this); + public void Render(Visual visual) => ImmediateRenderer.Render(visual, this); /// /// Creates a platform-specific implementation for a . diff --git a/src/Avalonia.Base/Media/Immutable/ImmutableVisualBrush.cs b/src/Avalonia.Base/Media/Immutable/ImmutableVisualBrush.cs index b436dcdb5e..1e0133c9b7 100644 --- a/src/Avalonia.Base/Media/Immutable/ImmutableVisualBrush.cs +++ b/src/Avalonia.Base/Media/Immutable/ImmutableVisualBrush.cs @@ -25,7 +25,7 @@ namespace Avalonia.Media.Immutable /// The tile mode. /// Controls the quality of interpolation. public ImmutableVisualBrush( - IVisual visual, + Visual visual, AlignmentX alignmentX = AlignmentX.Center, AlignmentY alignmentY = AlignmentY.Center, RelativeRect? destinationRect = null, @@ -62,6 +62,6 @@ namespace Avalonia.Media.Immutable } /// - public IVisual Visual { get; } + public Visual Visual { get; } } } diff --git a/src/Avalonia.Base/Media/VisualBrush.cs b/src/Avalonia.Base/Media/VisualBrush.cs index c665507652..8dce79cf11 100644 --- a/src/Avalonia.Base/Media/VisualBrush.cs +++ b/src/Avalonia.Base/Media/VisualBrush.cs @@ -4,15 +4,15 @@ using Avalonia.VisualTree; namespace Avalonia.Media { /// - /// Paints an area with an . + /// Paints an area with an . /// public class VisualBrush : TileBrush, IVisualBrush { /// /// Defines the property. /// - public static readonly StyledProperty VisualProperty = - AvaloniaProperty.Register(nameof(Visual)); + public static readonly StyledProperty VisualProperty = + AvaloniaProperty.Register(nameof(Visual)); static VisualBrush() { @@ -30,7 +30,7 @@ namespace Avalonia.Media /// Initializes a new instance of the class. /// /// The visual to draw. - public VisualBrush(IVisual visual) + public VisualBrush(Visual visual) { Visual = visual; } @@ -38,7 +38,7 @@ namespace Avalonia.Media /// /// Gets or sets the visual to draw. /// - public IVisual Visual + public Visual Visual { get { return GetValue(VisualProperty); } set { SetValue(VisualProperty, value); } diff --git a/src/Avalonia.Base/Platform/AssetLoader.cs b/src/Avalonia.Base/Platform/AssetLoader.cs index a74da2a178..8d3576d180 100644 --- a/src/Avalonia.Base/Platform/AssetLoader.cs +++ b/src/Avalonia.Base/Platform/AssetLoader.cs @@ -3,16 +3,22 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; +#if !BUILDTASK using Avalonia.Platform.Internal; using Avalonia.Utilities; +#endif namespace Avalonia.Platform { /// /// Loads assets compiled into the application binary. /// - public class AssetLoader : IAssetLoader + public class AssetLoader +#if !BUILDTASK + : IAssetLoader +#endif { +#if !BUILDTASK private static IAssemblyDescriptorResolver s_assemblyDescriptorResolver = new AssemblyDescriptorResolver(); private AssemblyDescriptor? _defaultResmAssembly; @@ -206,7 +212,8 @@ namespace Avalonia.Platform return null; } - +#endif + public static void RegisterResUriParsers() { if (!UriParser.IsKnownScheme("avares")) diff --git a/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs b/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs index 1d8f6b8408..93fffb3755 100644 --- a/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs +++ b/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs @@ -262,7 +262,7 @@ namespace Avalonia.PropertyStore private class UncommonFields { - public Func? _coerce; + public Func? _coerce; public T? _uncoercedValue; public T? _uncoercedBaseValue; } diff --git a/src/Avalonia.Base/PropertyStore/FramePriority.cs b/src/Avalonia.Base/PropertyStore/FramePriority.cs new file mode 100644 index 0000000000..950a8375f2 --- /dev/null +++ b/src/Avalonia.Base/PropertyStore/FramePriority.cs @@ -0,0 +1,36 @@ +using System.Diagnostics; +using Avalonia.Data; + +namespace Avalonia.PropertyStore +{ + internal enum FramePriority : sbyte + { + Animation, + AnimationTemplatedParentTheme, + AnimationTheme, + StyleTrigger, + StyleTriggerTemplatedParentTheme, + StyleTriggerTheme, + Template, + TemplateTemplatedParentTheme, + TemplateTheme, + Style, + StyleTemplatedParentTheme, + StyleTheme, + } + + internal static class FramePriorityExtensions + { + public static FramePriority ToFramePriority(this BindingPriority priority, FrameType type = FrameType.Style) + { + Debug.Assert(priority != BindingPriority.LocalValue); + var p = (int)(priority > 0 ? priority : priority + 1); + return (FramePriority)(p * 3 + (int)type); + } + + public static bool IsType(this FramePriority priority, FrameType type) + { + return (FrameType)((int)priority % 3) == type; + } + } +} diff --git a/src/Avalonia.Base/PropertyStore/ImmediateValueFrame.cs b/src/Avalonia.Base/PropertyStore/ImmediateValueFrame.cs index 1d886e7501..50d5333b9f 100644 --- a/src/Avalonia.Base/PropertyStore/ImmediateValueFrame.cs +++ b/src/Avalonia.Base/PropertyStore/ImmediateValueFrame.cs @@ -7,11 +7,11 @@ namespace Avalonia.PropertyStore /// Holds values in a set by one of the SetValue or AddBinding /// overloads with non-LocalValue priority. /// - internal class ImmediateValueFrame : ValueFrame + internal sealed class ImmediateValueFrame : ValueFrame { public ImmediateValueFrame(BindingPriority priority) + : base(priority, FrameType.Style) { - Priority = priority; } public TypedBindingEntry AddBinding( diff --git a/src/Avalonia.Base/PropertyStore/UntypedValueUtils.cs b/src/Avalonia.Base/PropertyStore/UntypedValueUtils.cs index acaecc0d52..a75ff27164 100644 --- a/src/Avalonia.Base/PropertyStore/UntypedValueUtils.cs +++ b/src/Avalonia.Base/PropertyStore/UntypedValueUtils.cs @@ -23,18 +23,6 @@ namespace Avalonia.PropertyStore 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( StyledPropertyBase property, object? value, diff --git a/src/Avalonia.Base/PropertyStore/ValueFrame.cs b/src/Avalonia.Base/PropertyStore/ValueFrame.cs index 5ada4b3c84..7a9d1bb13a 100644 --- a/src/Avalonia.Base/PropertyStore/ValueFrame.cs +++ b/src/Avalonia.Base/PropertyStore/ValueFrame.cs @@ -1,13 +1,18 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using Avalonia.Data; using Avalonia.Utilities; -using static Avalonia.Rendering.Composition.Animations.PropertySetSnapshot; namespace Avalonia.PropertyStore { + internal enum FrameType + { + Style, + TemplatedParentTheme, + Theme, + } + internal abstract class ValueFrame { private List? _entries; @@ -15,11 +20,18 @@ namespace Avalonia.PropertyStore private ValueStore? _owner; private bool _isShared; + protected ValueFrame(BindingPriority priority, FrameType type) + { + Priority = priority; + FramePriority = priority.ToFramePriority(type); + } + public int EntryCount => _index.Count; public bool IsActive => GetIsActive(out _); public ValueStore? Owner => !_isShared ? _owner : throw new AvaloniaInternalException("Cannot get owner for shared ValueFrame"); - public BindingPriority Priority { get; protected set; } + public BindingPriority Priority { get; } + public FramePriority FramePriority { get; } public bool Contains(AvaloniaProperty property) => _index.ContainsKey(property); diff --git a/src/Avalonia.Base/PropertyStore/ValueStore.cs b/src/Avalonia.Base/PropertyStore/ValueStore.cs index d858e30212..92e5288255 100644 --- a/src/Avalonia.Base/PropertyStore/ValueStore.cs +++ b/src/Avalonia.Base/PropertyStore/ValueStore.cs @@ -2,10 +2,12 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Linq; using Avalonia.Data; using Avalonia.Diagnostics; -using Avalonia.Logging; +using Avalonia.Styling; using Avalonia.Utilities; +using static Avalonia.Rendering.Composition.Animations.PropertySetSnapshot; namespace Avalonia.PropertyStore { @@ -580,6 +582,53 @@ namespace Avalonia.PropertyStore return false; } + public void RemoveFrames(FrameType type) + { + var removed = false; + + for (var i = _frames.Count - 1; i >= 0; --i) + { + var frame = _frames[i]; + + if (frame is not ImmediateValueFrame && frame.FramePriority.IsType(type)) + { + _frames.RemoveAt(i); + frame.Dispose(); + removed = true; + } + } + + if (removed) + { + ++_frameGeneration; + ReevaluateEffectiveValues(); + } + } + + + public void RemoveFrames(IReadOnlyList styles) + { + var removed = false; + + for (var i = _frames.Count - 1; i >= 0; --i) + { + var frame = _frames[i]; + + if (frame is StyleInstance style && styles.Contains(style.Source)) + { + _frames.RemoveAt(i); + frame.Dispose(); + removed = true; + } + } + + if (removed) + { + ++_frameGeneration; + ReevaluateEffectiveValues(); + } + } + public AvaloniaPropertyValue GetDiagnostic(AvaloniaProperty property) { object? value; @@ -612,7 +661,7 @@ namespace Avalonia.PropertyStore { Debug.Assert(!_frames.Contains(frame)); - var index = BinarySearchFrame(frame.Priority); + var index = BinarySearchFrame(frame.FramePriority); _frames.Insert(index, frame); ++_frameGeneration; frame.SetOwner(this); @@ -626,7 +675,7 @@ namespace Avalonia.PropertyStore { Debug.Assert(priority != BindingPriority.LocalValue); - var index = BinarySearchFrame(priority); + var index = BinarySearchFrame(priority.ToFramePriority()); if (index > 0 && _frames[index - 1] is ImmediateValueFrame f && f.Priority == priority && @@ -914,7 +963,7 @@ namespace Avalonia.PropertyStore } } - private int BinarySearchFrame(BindingPriority priority) + private int BinarySearchFrame(FramePriority priority) { var lo = 0; var hi = _frames.Count - 1; @@ -923,7 +972,7 @@ namespace Avalonia.PropertyStore while (lo <= hi) { var i = lo + ((hi - lo) >> 1); - var order = priority - _frames[i].Priority; + var order = priority - _frames[i].FramePriority; if (order <= 0) { diff --git a/src/Avalonia.Base/Reactive/AvaloniaPropertyBindingObservable.cs b/src/Avalonia.Base/Reactive/AvaloniaPropertyBindingObservable.cs index 9f038214c4..0789684eff 100644 --- a/src/Avalonia.Base/Reactive/AvaloniaPropertyBindingObservable.cs +++ b/src/Avalonia.Base/Reactive/AvaloniaPropertyBindingObservable.cs @@ -6,15 +6,15 @@ namespace Avalonia.Reactive { internal class AvaloniaPropertyBindingObservable : LightweightObservableBase>, IDescription { - private readonly WeakReference _target; + private readonly WeakReference _target; private readonly AvaloniaProperty _property; private BindingValue _value = BindingValue.Unset; public AvaloniaPropertyBindingObservable( - IAvaloniaObject target, + AvaloniaObject target, AvaloniaProperty property) { - _target = new WeakReference(target); + _target = new WeakReference(target); _property = property; } diff --git a/src/Avalonia.Base/Reactive/AvaloniaPropertyChangedObservable.cs b/src/Avalonia.Base/Reactive/AvaloniaPropertyChangedObservable.cs index dc29ddbdff..fa876a8906 100644 --- a/src/Avalonia.Base/Reactive/AvaloniaPropertyChangedObservable.cs +++ b/src/Avalonia.Base/Reactive/AvaloniaPropertyChangedObservable.cs @@ -6,14 +6,14 @@ namespace Avalonia.Reactive LightweightObservableBase, IDescription { - private readonly WeakReference _target; + private readonly WeakReference _target; private readonly AvaloniaProperty _property; public AvaloniaPropertyChangedObservable( - IAvaloniaObject target, + AvaloniaObject target, AvaloniaProperty property) { - _target = new WeakReference(target); + _target = new WeakReference(target); _property = property; } diff --git a/src/Avalonia.Base/Reactive/AvaloniaPropertyObservable.cs b/src/Avalonia.Base/Reactive/AvaloniaPropertyObservable.cs index cafce58c9c..a4fa587a50 100644 --- a/src/Avalonia.Base/Reactive/AvaloniaPropertyObservable.cs +++ b/src/Avalonia.Base/Reactive/AvaloniaPropertyObservable.cs @@ -6,15 +6,15 @@ namespace Avalonia.Reactive { internal class AvaloniaPropertyObservable : LightweightObservableBase, IDescription { - private readonly WeakReference _target; + private readonly WeakReference _target; private readonly AvaloniaProperty _property; private Optional _value; public AvaloniaPropertyObservable( - IAvaloniaObject target, + AvaloniaObject target, AvaloniaProperty property) { - _target = new WeakReference(target); + _target = new WeakReference(target); _property = property; } @@ -49,31 +49,23 @@ namespace Avalonia.Reactive { if (e.Property == _property) { - if (e.Sender is AvaloniaObject ao) - { - T newValue; - - if (e is AvaloniaPropertyChangedEventArgs typed) - { - newValue = AvaloniaObjectExtensions.GetValue(ao, typed.Property); - } - else - { - newValue = (T)e.Sender.GetValue(e.Property)!; - } + T newValue; - if (!_value.HasValue || - !EqualityComparer.Default.Equals(newValue, _value.Value)) - { - _value = newValue; - PublishNext(_value.Value!); - } + if (e is AvaloniaPropertyChangedEventArgs typed) + { + newValue = AvaloniaObjectExtensions.GetValue(e.Sender, typed.Property); } else { - throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported."); + newValue = (T)e.Sender.GetValue(e.Property)!; } + if (!_value.HasValue || + !EqualityComparer.Default.Equals(newValue, _value.Value)) + { + _value = newValue; + PublishNext(_value.Value!); + } } } } diff --git a/src/Avalonia.Base/Rect.cs b/src/Avalonia.Base/Rect.cs index 7930228d99..a91b089a33 100644 --- a/src/Avalonia.Base/Rect.cs +++ b/src/Avalonia.Base/Rect.cs @@ -243,7 +243,7 @@ namespace Avalonia } /// - /// Determines whether a point in in the bounds of the rectangle. + /// Determines whether a point is in the bounds of the rectangle. /// /// The point. /// true if the point is in the bounds of the rectangle; otherwise false. diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimationInstance.cs b/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimationInstance.cs index e20a4a9ad8..70462dd37b 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimationInstance.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimationInstance.cs @@ -128,8 +128,16 @@ namespace Avalonia.Rendering.Composition.Animations left = kf; right = _keyFrames[c + 1]; + } + else if (c == 0) + { + // The current progress is before the first frame, we implicitly use the starting value + // as the first frame in this case + right = _keyFrames[c]; break; } + else + break; } var keyProgress = Math.Max(0, Math.Min(1, (iterationProgress - left.Key) / (right.Key - left.Key))); diff --git a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs index 98a6a3600e..6130f36983 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs @@ -45,7 +45,7 @@ public class CompositingRenderer : IRendererWithCompositor _compositor = compositor; _recordingContext = new DrawingContext(_recorder); CompositionTarget = compositor.CreateCompositionTarget(root.CreateRenderTarget); - CompositionTarget.Root = ((Visual)root!.VisualRoot!).AttachToCompositor(compositor); + CompositionTarget.Root = ((Visual)root).AttachToCompositor(compositor); _update = Update; } @@ -75,7 +75,7 @@ public class CompositingRenderer : IRendererWithCompositor } /// - public void AddDirty(IVisual visual) + public void AddDirty(Visual visual) { if (_updating) throw new InvalidOperationException("Visual was invalidated during the render pass"); @@ -84,7 +84,7 @@ public class CompositingRenderer : IRendererWithCompositor } /// - public IEnumerable HitTest(Point p, IVisual root, Func? filter) + public IEnumerable HitTest(Point p, Visual root, Func? filter) { Func? f = null; if (filter != null) @@ -109,14 +109,14 @@ public class CompositingRenderer : IRendererWithCompositor } /// - public IVisual? HitTestFirst(Point p, IVisual root, Func? filter) + public Visual? HitTestFirst(Point p, Visual root, Func? filter) { // TODO: Optimize return HitTest(p, root, filter).FirstOrDefault(); } /// - public void RecalculateChildren(IVisual visual) + public void RecalculateChildren(Visual visual) { if (_updating) throw new InvalidOperationException("Visual was invalidated during the render pass"); @@ -130,9 +130,9 @@ public class CompositingRenderer : IRendererWithCompositor if(v.CompositionVisual == null) return; var compositionChildren = v.CompositionVisual.Children; - var visualChildren = (AvaloniaList)v.GetVisualChildren(); + var visualChildren = (AvaloniaList)v.GetVisualChildren(); - PooledList<(IVisual visual, int index)>? sortedChildren = null; + PooledList<(Visual visual, int index)>? sortedChildren = null; if (v.HasNonUniformZIndexChildren && visualChildren.Count > 1) { sortedChildren = new (visualChildren.Count); @@ -146,8 +146,18 @@ public class CompositingRenderer : IRendererWithCompositor return result == 0 ? lhs.index.CompareTo(rhs.index) : result; }); } - - if (compositionChildren.Count == visualChildren.Count) + + var childVisual = v.ChildCompositionVisual; + + // Check if the current visual somehow got migrated to another compositor + if (childVisual != null && childVisual.Compositor != v.CompositionVisual.Compositor) + childVisual = null; + + var expectedCount = visualChildren.Count; + if (childVisual != null) + expectedCount++; + + if (compositionChildren.Count == expectedCount) { bool mismatch = false; if (sortedChildren != null) @@ -167,6 +177,9 @@ public class CompositingRenderer : IRendererWithCompositor break; } + if (childVisual != null && + !ReferenceEquals(compositionChildren[compositionChildren.Count - 1], childVisual)) + mismatch = true; if (!mismatch) { @@ -193,6 +206,9 @@ public class CompositingRenderer : IRendererWithCompositor if (compositionChild != null) compositionChildren.Add(compositionChild); } + + if (childVisual != null) + compositionChildren.Add(childVisual); } private void UpdateCore() diff --git a/src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs b/src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs index 1a13d23acd..00fa7b3315 100644 --- a/src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs +++ b/src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs @@ -29,4 +29,7 @@ public partial class Compositor public ImplicitAnimationCollection CreateImplicitAnimationCollection() => new ImplicitAnimationCollection(this); public CompositionAnimationGroup CreateAnimationGroup() => new CompositionAnimationGroup(this); + + public CompositionSolidColorVisual CreateSolidColorVisual() => + new(this, new ServerCompositionSolidColorVisual(Server)); } \ No newline at end of file diff --git a/src/Avalonia.Base/Rendering/Composition/ElementCompositionPreview.cs b/src/Avalonia.Base/Rendering/Composition/ElementCompositionPreview.cs index 5bd8e4a4d3..b01321edd8 100644 --- a/src/Avalonia.Base/Rendering/Composition/ElementCompositionPreview.cs +++ b/src/Avalonia.Base/Rendering/Composition/ElementCompositionPreview.cs @@ -1,5 +1,8 @@ // Special license applies License.md +using System; +using Avalonia.VisualTree; + namespace Avalonia.Rendering.Composition; /// @@ -13,4 +16,22 @@ public static class ElementComposition /// /// public static CompositionVisual? GetElementVisual(Visual visual) => visual.CompositionVisual; + + /// + /// Sets a custom as the last child of the element’s visual tree. + /// + public static void SetElementChildVisual(Visual visual, CompositionVisual? compositionVisual) + { + if (compositionVisual != null && visual.CompositionVisual != null && + compositionVisual.Compositor != visual.CompositionVisual.Compositor) + throw new InvalidOperationException("Composition visuals belong to different compositor instances"); + + visual.ChildCompositionVisual = compositionVisual; + visual.GetVisualRoot()?.Renderer.RecalculateChildren(visual); + } + + /// + /// Retrieves a object previously set by a call to . + /// + public static CompositionVisual? GetElementChildVisual(Visual visual) => visual.ChildCompositionVisual; } diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionSolidColorVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionSolidColorVisual.cs new file mode 100644 index 0000000000..79abd7ee17 --- /dev/null +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionSolidColorVisual.cs @@ -0,0 +1,11 @@ +using Avalonia.Media.Immutable; + +namespace Avalonia.Rendering.Composition.Server; + +internal partial class ServerCompositionSolidColorVisual +{ + protected override void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip) + { + canvas.DrawRectangle(new ImmutableSolidColorBrush(Color), null, new Rect(0, 0, Size.X, Size.Y)); + } +} \ No newline at end of file diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs index b742f1d44a..d724e14298 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs @@ -48,7 +48,7 @@ namespace Avalonia.Rendering.Composition.Server if (Opacity != 1) canvas.PushOpacity(Opacity); var boundsRect = new Rect(new Size(Size.X, Size.Y)); - if(ClipToBounds) + if (ClipToBounds && !HandlesClipToBounds) canvas.PushClip(Root!.SnapToDevicePixels(boundsRect)); if (Clip != null) canvas.PushGeometryClip(Clip); @@ -65,11 +65,13 @@ namespace Avalonia.Rendering.Composition.Server canvas.PopOpacityMask(); if (Clip != null) canvas.PopGeometryClip(); - if (ClipToBounds) + if (ClipToBounds && !HandlesClipToBounds) canvas.PopClip(); if(Opacity != 1) canvas.PopOpacity(); } + + protected virtual bool HandlesClipToBounds => false; private ReadbackData _readback0, _readback1, _readback2; diff --git a/src/Avalonia.Base/Rendering/DeferredRenderer.cs b/src/Avalonia.Base/Rendering/DeferredRenderer.cs index 4236763e3b..971702269c 100644 --- a/src/Avalonia.Base/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Base/Rendering/DeferredRenderer.cs @@ -22,14 +22,14 @@ namespace Avalonia.Rendering { private readonly IDispatcher? _dispatcher; private readonly IRenderLoop? _renderLoop; - private readonly IVisual _root; + private readonly Visual _root; private readonly ISceneBuilder _sceneBuilder; private bool _running; private bool _disposed; private volatile IRef? _scene; private DirtyVisuals? _dirty; - private HashSet? _recalculateChildren; + private HashSet? _recalculateChildren; private IRef? _overlay; private int _lastSceneId = -1; private DisplayDirtyRects _dirtyRectsDisplay = new DisplayDirtyRects(); @@ -56,7 +56,7 @@ namespace Avalonia.Rendering IDeferredRendererLock? rendererLock = null) : base(true) { _dispatcher = dispatcher ?? Dispatcher.UIThread; - _root = root ?? throw new ArgumentNullException(nameof(root)); + _root = root as Visual ?? throw new ArgumentNullException(nameof(root)); _sceneBuilder = sceneBuilder ?? new SceneBuilder(); Layers = new RenderLayers(); _renderLoop = renderLoop; @@ -74,7 +74,7 @@ namespace Avalonia.Rendering /// This constructor is intended to be used for unit testing. /// public DeferredRenderer( - IVisual root, + Visual root, IRenderTarget renderTarget, ISceneBuilder? sceneBuilder = null) : base(true) { @@ -116,7 +116,7 @@ namespace Avalonia.Rendering internal IRenderTarget? RenderTarget { get; private set; } /// - public void AddDirty(IVisual visual) + public void AddDirty(Visual visual) { _dirty?.Add(visual); } @@ -142,7 +142,7 @@ namespace Avalonia.Rendering DisposeRenderTarget(); } - public void RecalculateChildren(IVisual visual) => _recalculateChildren?.Add(visual); + public void RecalculateChildren(Visual visual) => _recalculateChildren?.Add(visual); void DisposeRenderTarget() { @@ -163,17 +163,17 @@ namespace Avalonia.Rendering } /// - public IEnumerable HitTest(Point p, IVisual root, Func? filter) + public IEnumerable HitTest(Point p, Visual root, Func? filter) { EnsureCanHitTest(); //It's safe to access _scene here without a lock since //it's only changed from UI thread which we are currently on - return _scene?.Item.HitTest(p, root, filter) ?? Enumerable.Empty(); + return _scene?.Item.HitTest(p, root, filter) ?? Enumerable.Empty(); } /// - public IVisual? HitTestFirst(Point p, IVisual root, Func? filter) + public Visual? HitTestFirst(Point p, Visual root, Func? filter) { 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) { @@ -643,7 +643,7 @@ namespace Avalonia.Rendering if (_dirty == null) { _dirty = new DirtyVisuals(); - _recalculateChildren = new HashSet(); + _recalculateChildren = new HashSet(); _sceneBuilder.UpdateAll(scene); } else diff --git a/src/Avalonia.Base/Rendering/DirtyVisuals.cs b/src/Avalonia.Base/Rendering/DirtyVisuals.cs index 999b12e810..dfadbdf614 100644 --- a/src/Avalonia.Base/Rendering/DirtyVisuals.cs +++ b/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 /// stored and if we reach that limit, assume all visuals are dirty. /// - internal class DirtyVisuals : IEnumerable + internal class DirtyVisuals : IEnumerable { - private SortedDictionary> _inner = new SortedDictionary>(); - private Dictionary _index = new Dictionary(); + private SortedDictionary> _inner = new SortedDictionary>(); + private Dictionary _index = new Dictionary(); private int _enumerating; /// @@ -28,14 +28,14 @@ namespace Avalonia.Rendering /// Adds a visual to the dirty list. /// /// The dirty visual. - public void Add(IVisual visual) + public void Add(Visual visual) { if (_enumerating > 0) { 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)) { @@ -50,7 +50,7 @@ namespace Avalonia.Rendering if (!_inner.TryGetValue(distance, out var list)) { - list = new List(); + list = new List(); _inner.Add(distance, list); } @@ -76,7 +76,7 @@ namespace Avalonia.Rendering /// Gets the dirty visuals, in ascending order of distance to their root. /// /// A collection of visuals. - public IEnumerator GetEnumerator() + public IEnumerator GetEnumerator() { _enumerating++; try diff --git a/src/Avalonia.Base/Rendering/ICustomSimpleHitTest.cs b/src/Avalonia.Base/Rendering/ICustomSimpleHitTest.cs index 354a344ffe..fae595413f 100644 --- a/src/Avalonia.Base/Rendering/ICustomSimpleHitTest.cs +++ b/src/Avalonia.Base/Rendering/ICustomSimpleHitTest.cs @@ -24,10 +24,10 @@ namespace Avalonia.Rendering 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; - public static bool HitTestCustom(this IEnumerable children, Point point) + public static bool HitTestCustom(this IEnumerable children, Point point) => children.Any(ctrl => ctrl.HitTestCustom(point)); } } diff --git a/src/Avalonia.Base/Rendering/IRenderRoot.cs b/src/Avalonia.Base/Rendering/IRenderRoot.cs index 1aa44158b2..aa06ce7bed 100644 --- a/src/Avalonia.Base/Rendering/IRenderRoot.cs +++ b/src/Avalonia.Base/Rendering/IRenderRoot.cs @@ -8,7 +8,7 @@ namespace Avalonia.Rendering /// Represents the root of a renderable tree. /// [NotClientImplementable] - public interface IRenderRoot : IVisual + public interface IRenderRoot { /// /// Gets the client size of the window. diff --git a/src/Avalonia.Base/Rendering/IRenderer.cs b/src/Avalonia.Base/Rendering/IRenderer.cs index 8d6aabf440..cb36aad7cc 100644 --- a/src/Avalonia.Base/Rendering/IRenderer.cs +++ b/src/Avalonia.Base/Rendering/IRenderer.cs @@ -34,7 +34,7 @@ namespace Avalonia.Rendering /// Mark a visual as dirty and needing re-rendering. /// /// The visual. - void AddDirty(IVisual visual); + void AddDirty(Visual visual); /// /// 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. /// /// The visuals at the specified point, topmost first. - IEnumerable HitTest(Point p, IVisual root, Func filter); + IEnumerable HitTest(Point p, Visual root, Func filter); /// /// 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. /// /// The visual at the specified point, topmost first. - IVisual? HitTestFirst(Point p, IVisual root, Func filter); + Visual? HitTestFirst(Point p, Visual root, Func filter); /// /// Informs the renderer that the z-ordering of a visual's children has changed. /// /// The visual. - void RecalculateChildren(IVisual visual); + void RecalculateChildren(Visual visual); /// /// Called when a resize notification is received by the control being rendered. diff --git a/src/Avalonia.Base/Rendering/ImmediateRenderer.cs b/src/Avalonia.Base/Rendering/ImmediateRenderer.cs index 79ef52586d..9e1582ed43 100644 --- a/src/Avalonia.Base/Rendering/ImmediateRenderer.cs +++ b/src/Avalonia.Base/Rendering/ImmediateRenderer.cs @@ -18,7 +18,7 @@ namespace Avalonia.Rendering /// public class ImmediateRenderer : RendererBase, IRenderer, IVisualBrushRenderer { - private readonly IVisual _root; + private readonly Visual _root; private readonly IRenderRoot? _renderRoot; private bool _updateTransformedBounds = true; private IRenderTarget? _renderTarget; @@ -27,13 +27,13 @@ namespace Avalonia.Rendering /// Initializes a new instance of the class. /// /// The control to render. - public ImmediateRenderer(IVisual root) + public ImmediateRenderer(Visual root) { _root = root ?? throw new ArgumentNullException(nameof(root)); _renderRoot = root as IRenderRoot; } - private ImmediateRenderer(IVisual root, bool updateTransformedBounds) + private ImmediateRenderer(Visual root, bool updateTransformedBounds) { _root = root ?? throw new ArgumentNullException(nameof(root)); _renderRoot = root as IRenderRoot; @@ -102,7 +102,7 @@ namespace Avalonia.Rendering /// /// The visual. /// The render target. - 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 context = new DrawingContext(target.CreateDrawingContext(renderer))) @@ -116,7 +116,7 @@ namespace Avalonia.Rendering /// /// The visual. /// The drawing context. - public static void Render(IVisual visual, DrawingContext context) + public static void Render(Visual visual, DrawingContext context) { using (var renderer = new ImmediateRenderer(visual, updateTransformedBounds: false)) { @@ -125,7 +125,7 @@ namespace Avalonia.Rendering } /// - public void AddDirty(IVisual visual) + public void AddDirty(Visual visual) { if (visual.Bounds != Rect.Empty) { @@ -162,18 +162,18 @@ namespace Avalonia.Rendering } /// - public IEnumerable HitTest(Point p, IVisual root, Func filter) + public IEnumerable HitTest(Point p, Visual root, Func filter) { return HitTest(root, p, filter); } - public IVisual? HitTestFirst(Point p, IVisual root, Func filter) + public Visual? HitTestFirst(Point p, Visual root, Func filter) { return HitTest(root, p, filter).FirstOrDefault(); } /// - public void RecalculateChildren(IVisual visual) => AddDirty(visual); + public void RecalculateChildren(Visual visual) => AddDirty(visual); /// public void Start() @@ -199,21 +199,21 @@ namespace Avalonia.Rendering 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); renderer.Render(context, visual, visual.Bounds); } - private static void ClearTransformedBounds(IVisual visual) + private static void ClearTransformedBounds(Visual visual) { 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) { @@ -228,10 +228,10 @@ namespace Avalonia.Rendering } } - private static IEnumerable HitTest( - IVisual visual, + private static IEnumerable HitTest( + Visual visual, Point p, - Func? filter) + Func? filter) { _ = 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 clipToBounds = visual.ClipToBounds; @@ -329,11 +329,11 @@ namespace Avalonia.Rendering #pragma warning restore 0618 if (_updateTransformedBounds) - visual.TransformedBounds = transformed; + visual.SetTransformedBounds(transformed); var childrenEnumerable = visual.HasNonUniformZIndexChildren ? visual.VisualChildren.OrderBy(x => x, ZIndexComparer.Instance) - : (IEnumerable)visual.VisualChildren; + : (IEnumerable)visual.VisualChildren; foreach (var child in childrenEnumerable) { diff --git a/src/Avalonia.Base/Rendering/RenderLayer.cs b/src/Avalonia.Base/Rendering/RenderLayer.cs index 768e832fe5..d1e3fcafb1 100644 --- a/src/Avalonia.Base/Rendering/RenderLayer.cs +++ b/src/Avalonia.Base/Rendering/RenderLayer.cs @@ -11,7 +11,7 @@ namespace Avalonia.Rendering IDrawingContextImpl drawingContext, Size size, double scaling, - IVisual layerRoot) + Visual layerRoot) { Bitmap = RefCountable.Create(drawingContext.CreateLayer(size)); Size = size; @@ -24,7 +24,7 @@ namespace Avalonia.Rendering public bool IsEmpty { get; set; } public double Scaling { 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) { diff --git a/src/Avalonia.Base/Rendering/RenderLayers.cs b/src/Avalonia.Base/Rendering/RenderLayers.cs index 7583886835..eff81e6bbf 100644 --- a/src/Avalonia.Base/Rendering/RenderLayers.cs +++ b/src/Avalonia.Base/Rendering/RenderLayers.cs @@ -3,17 +3,16 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using Avalonia.Platform; using Avalonia.Rendering.SceneGraph; -using Avalonia.VisualTree; namespace Avalonia.Rendering { public class RenderLayers : IEnumerable { private readonly List _inner = new List(); - private readonly Dictionary _index = new Dictionary(); + private readonly Dictionary _index = new Dictionary(); 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) { @@ -59,7 +58,7 @@ namespace Avalonia.Rendering _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); } diff --git a/src/Avalonia.Base/Rendering/SceneGraph/ISceneBuilder.cs b/src/Avalonia.Base/Rendering/SceneGraph/ISceneBuilder.cs index dd993fbb8c..f469fdbfe8 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/ISceneBuilder.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/ISceneBuilder.cs @@ -19,6 +19,6 @@ namespace Avalonia.Rendering.SceneGraph /// The scene. /// The visual to update. /// True if changes were made, otherwise false. - bool Update(Scene scene, IVisual visual); + bool Update(Scene scene, Visual visual); } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/SceneGraph/IVisualNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/IVisualNode.cs index e042236346..59a032748d 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/IVisualNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/IVisualNode.cs @@ -2,19 +2,18 @@ using System.Collections.Generic; using Avalonia.Platform; using Avalonia.Utilities; -using Avalonia.VisualTree; namespace Avalonia.Rendering.SceneGraph { /// - /// Represents a node in the low-level scene graph representing an . + /// Represents a node in the low-level scene graph representing a . /// public interface IVisualNode : IDisposable { /// /// Gets the visual to which the node relates. /// - IVisual Visual { get; } + Visual Visual { get; } /// /// Gets the parent scene graph node. diff --git a/src/Avalonia.Base/Rendering/SceneGraph/Scene.cs b/src/Avalonia.Base/Rendering/SceneGraph/Scene.cs index c436299018..2da8923497 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/Scene.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/Scene.cs @@ -13,24 +13,24 @@ namespace Avalonia.Rendering.SceneGraph /// public class Scene : IDisposable { - private readonly Dictionary _index; + private readonly Dictionary _index; private readonly TaskCompletionSource _rendered = new TaskCompletionSource(); /// /// Initializes a new instance of the class. /// /// The root visual to draw. - public Scene(IVisual rootVisual) + public Scene(Visual rootVisual) : this( new VisualNode(rootVisual, null), - new Dictionary(), + new Dictionary(), new SceneLayers(rootVisual), 0) { _index.Add(rootVisual, Root); } - private Scene(VisualNode root, Dictionary index, SceneLayers layers, int generation) + private Scene(VisualNode root, Dictionary index, SceneLayers layers, int generation) { _ = root ?? throw new ArgumentNullException(nameof(root)); @@ -87,7 +87,7 @@ namespace Avalonia.Rendering.SceneGraph /// The cloned scene. public Scene CloneScene() { - var index = new Dictionary(_index.Count); + var index = new Dictionary(_index.Count); var root = Clone((VisualNode)Root, null, index); var result = new Scene(root, index, Layers.Clone(), Generation + 1) @@ -115,7 +115,7 @@ namespace Avalonia.Rendering.SceneGraph /// /// The node representing the visual or null if it could not be found. /// - public IVisualNode? FindNode(IVisual visual) + public IVisualNode? FindNode(Visual visual) { _index.TryGetValue(visual, out var node); return node; @@ -128,10 +128,10 @@ namespace Avalonia.Rendering.SceneGraph /// The root of the subtree to search. /// A filter. May be null. /// The visuals at the specified point. - public IEnumerable HitTest(Point p, IVisual root, Func? filter) + public IEnumerable HitTest(Point p, Visual root, Func? filter) { var node = FindNode(root); - return (node != null) ? new HitTestEnumerable(node, filter, p, Root) : Enumerable.Empty(); + return (node != null) ? new HitTestEnumerable(node, filter, p, Root) : Enumerable.Empty(); } /// @@ -141,7 +141,7 @@ namespace Avalonia.Rendering.SceneGraph /// The root of the subtree to search. /// A filter. May be null. /// The visual at the specified point. - public IVisual? HitTestFirst(Point p, IVisual root, Func? filter) + public Visual? HitTestFirst(Point p, Visual root, Func? filter) { var node = FindNode(root); return (node != null) ? HitTestFirst(node, p, filter) : null; @@ -160,7 +160,7 @@ namespace Avalonia.Rendering.SceneGraph node.Dispose(); } - private VisualNode Clone(VisualNode source, IVisualNode? parent, Dictionary index) + private VisualNode Clone(VisualNode source, IVisualNode? parent, Dictionary index) { var result = source.Clone(parent); @@ -184,7 +184,7 @@ namespace Avalonia.Rendering.SceneGraph return result; } - private IVisual HitTestFirst(IVisualNode root, Point p, Func? filter) + private Visual HitTestFirst(IVisualNode root, Point p, Func? filter) { using var enumerator = new HitTestEnumerator(root, filter, p, Root); @@ -193,14 +193,14 @@ namespace Avalonia.Rendering.SceneGraph return enumerator.Current; } - private class HitTestEnumerable : IEnumerable + private class HitTestEnumerable : IEnumerable { private readonly IVisualNode _root; - private readonly Func? _filter; + private readonly Func? _filter; private readonly IVisualNode _sceneRoot; private readonly Point _point; - public HitTestEnumerable(IVisualNode root, Func? filter, Point point, IVisualNode sceneRoot) + public HitTestEnumerable(IVisualNode root, Func? filter, Point point, IVisualNode sceneRoot) { _root = root; _filter = filter; @@ -208,7 +208,7 @@ namespace Avalonia.Rendering.SceneGraph _sceneRoot = sceneRoot; } - public IEnumerator GetEnumerator() + public IEnumerator GetEnumerator() { return new HitTestEnumerator(_root, _filter, _point, _sceneRoot); } @@ -219,15 +219,15 @@ namespace Avalonia.Rendering.SceneGraph } } - private struct HitTestEnumerator : IEnumerator + private struct HitTestEnumerator : IEnumerator { private readonly PooledStack _nodeStack; - private readonly Func? _filter; + private readonly Func? _filter; private readonly IVisualNode _sceneRoot; - private IVisual? _current; + private Visual? _current; private readonly Point _point; - public HitTestEnumerator(IVisualNode root, Func? filter, Point point, IVisualNode sceneRoot) + public HitTestEnumerator(IVisualNode root, Func? filter, Point point, IVisualNode sceneRoot) { _nodeStack = new PooledStack(); _nodeStack.Push(new Entry(root, false, null, true)); @@ -282,7 +282,7 @@ namespace Avalonia.Rendering.SceneGraph throw new NotSupportedException(); } - public IVisual Current => _current!; + public Visual Current => _current!; object IEnumerator.Current => Current; diff --git a/src/Avalonia.Base/Rendering/SceneGraph/SceneBuilder.cs b/src/Avalonia.Base/Rendering/SceneGraph/SceneBuilder.cs index 0ceb44ed75..86b42c0c4b 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/SceneBuilder.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/SceneBuilder.cs @@ -30,7 +30,7 @@ namespace Avalonia.Rendering.SceneGraph } /// - public bool Update(Scene scene, IVisual visual) + public bool Update(Scene scene, Visual visual) { _ = scene ?? throw new ArgumentNullException(nameof(scene)); _ = visual ?? throw new ArgumentNullException(nameof(visual)); @@ -120,7 +120,7 @@ namespace Avalonia.Rendering.SceneGraph return false; } - private static VisualNode? FindExistingAncestor(Scene scene, IVisual visual) + private static VisualNode? FindExistingAncestor(Scene scene, Visual visual) { var node = scene.FindNode(visual); @@ -151,7 +151,7 @@ namespace Avalonia.Rendering.SceneGraph return (VisualNode)node; } - private static object GetOrCreateChildNode(Scene scene, IVisual child, VisualNode parent) + private static object GetOrCreateChildNode(Scene scene, Visual child, VisualNode parent) { var result = (VisualNode?)scene.FindNode(child); @@ -259,11 +259,11 @@ namespace Avalonia.Rendering.SceneGraph catch { } var transformed = new TransformedBounds(new Rect(visual.Bounds.Size), clip, node.Transform); - visual.TransformedBounds = transformed; + visual.SetTransformedBounds(transformed); if (forceRecurse) { - var visualChildren = (IList) visual.VisualChildren; + var visualChildren = (IList) visual.VisualChildren; node.TryPreallocateChildren(visualChildren.Count); @@ -278,7 +278,7 @@ namespace Avalonia.Rendering.SceneGraph if (visual.HasNonUniformZIndexChildren) { - var sortedChildren = new (IVisual visual, int index)[count]; + var sortedChildren = new (Visual visual, int index)[count]; for (var i = 0; i < count; i++) { @@ -352,7 +352,7 @@ namespace Avalonia.Rendering.SceneGraph } } - private static VisualNode CreateNode(Scene scene, IVisual visual, VisualNode parent) + private static VisualNode CreateNode(Scene scene, Visual visual, VisualNode parent) { var node = new VisualNode(visual, parent); node.LayerRoot = parent.LayerRoot; @@ -379,7 +379,7 @@ namespace Avalonia.Rendering.SceneGraph scene.Layers[node.LayerRoot!].Dirty.Add(node.Bounds); - node.Visual.TransformedBounds = null; + node.Visual.SetTransformedBounds(null); if (node.LayerRoot == node.Visual && node.Visual != scene.Root.Visual) { @@ -455,7 +455,7 @@ namespace Avalonia.Rendering.SceneGraph } // HACK: Disabled layers because they're broken in current renderer. See #2244. - private static bool ShouldStartLayer(IVisual visual) => false; + private static bool ShouldStartLayer(Visual visual) => false; private static IGeometryImpl? CreateLayerGeometryClip(VisualNode node) { diff --git a/src/Avalonia.Base/Rendering/SceneGraph/SceneLayer.cs b/src/Avalonia.Base/Rendering/SceneGraph/SceneLayer.cs index e9474f6e98..a5e3b88188 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/SceneLayer.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/SceneLayer.cs @@ -1,6 +1,5 @@ using Avalonia.Media; using Avalonia.Platform; -using Avalonia.VisualTree; namespace Avalonia.Rendering.SceneGraph { @@ -14,7 +13,7 @@ namespace Avalonia.Rendering.SceneGraph /// /// The visual at the root of the layer. /// The distance from the scene root. - public SceneLayer(IVisual layerRoot, int distanceFromRoot) + public SceneLayer(Visual layerRoot, int distanceFromRoot) { LayerRoot = layerRoot; Dirty = new DirtyRects(); @@ -39,7 +38,7 @@ namespace Avalonia.Rendering.SceneGraph /// /// Gets the visual at the root of the layer. /// - public IVisual LayerRoot { get; } + public Visual LayerRoot { get; } /// /// Gets the distance of the layer root from the root of the scene. diff --git a/src/Avalonia.Base/Rendering/SceneGraph/SceneLayers.cs b/src/Avalonia.Base/Rendering/SceneGraph/SceneLayers.cs index 16d704e5f6..8a997c5ace 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/SceneLayers.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/SceneLayers.cs @@ -10,15 +10,15 @@ namespace Avalonia.Rendering.SceneGraph /// public class SceneLayers : IEnumerable { - private readonly IVisual _root; + private readonly Visual _root; private readonly List _inner; - private readonly Dictionary _index; + private readonly Dictionary _index; /// /// Initializes a new instance of the class. /// /// The scene's root visual. - public SceneLayers(IVisual root) : this(root, 0) + public SceneLayers(Visual root) : this(root, 0) { } @@ -27,12 +27,12 @@ namespace Avalonia.Rendering.SceneGraph /// /// The scene's root visual. /// Initial layer capacity. - public SceneLayers(IVisual root, int capacity) + public SceneLayers(Visual root, int capacity) { _root = root; _inner = new List(capacity); - _index = new Dictionary(capacity); + _index = new Dictionary(capacity); } /// @@ -71,14 +71,14 @@ namespace Avalonia.Rendering.SceneGraph /// /// The layer's root visual. /// The layer. - public SceneLayer this[IVisual visual] => _index[visual]; + public SceneLayer this[Visual visual] => _index[visual]; /// /// Adds a layer to the scene. /// /// The root visual of the layer. /// The created layer. - public SceneLayer Add(IVisual layerRoot) + public SceneLayer Add(Visual layerRoot) { _ = layerRoot ?? throw new ArgumentNullException(nameof(layerRoot)); @@ -115,7 +115,7 @@ namespace Avalonia.Rendering.SceneGraph /// /// True if a layer exists with the specified root visual, otherwise false. /// - public bool Exists(IVisual layerRoot) + public bool Exists(Visual layerRoot) { _ = layerRoot ?? throw new ArgumentNullException(nameof(layerRoot)); @@ -127,7 +127,7 @@ namespace Avalonia.Rendering.SceneGraph /// /// The root visual. /// The layer if found, otherwise null. - public SceneLayer? Find(IVisual layerRoot) + public SceneLayer? Find(Visual layerRoot) { _index.TryGetValue(layerRoot, out var result); return result; @@ -138,7 +138,7 @@ namespace Avalonia.Rendering.SceneGraph /// /// The root visual. /// The layer. - public SceneLayer GetOrAdd(IVisual layerRoot) + public SceneLayer GetOrAdd(Visual layerRoot) { _ = layerRoot ?? throw new ArgumentNullException(nameof(layerRoot)); @@ -155,7 +155,7 @@ namespace Avalonia.Rendering.SceneGraph /// /// The root visual. /// True if a matching layer was removed, otherwise false. - public bool Remove(IVisual layerRoot) + public bool Remove(Visual layerRoot) { _ = layerRoot ?? throw new ArgumentNullException(nameof(layerRoot)); diff --git a/src/Avalonia.Base/Rendering/SceneGraph/VisualNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/VisualNode.cs index 8a2f1f5073..4eec214f4f 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/VisualNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/VisualNode.cs @@ -30,7 +30,7 @@ namespace Avalonia.Rendering.SceneGraph /// /// The visual that this node represents. /// The parent scene graph node, if any. - public VisualNode(IVisual visual, IVisualNode? parent) + public VisualNode(Visual visual, IVisualNode? parent) { Visual = visual ?? throw new ArgumentNullException(nameof(visual)); Parent = parent; @@ -39,7 +39,7 @@ namespace Avalonia.Rendering.SceneGraph } /// - public IVisual Visual { get; } + public Visual Visual { get; } /// public IVisualNode? Parent { get; } @@ -98,7 +98,7 @@ namespace Avalonia.Rendering.SceneGraph /// public bool OpacityChanged { get; private set; } - public IVisual? LayerRoot { get; set; } + public Visual? LayerRoot { get; set; } /// public IReadOnlyList Children => _children ?? EmptyChildren; diff --git a/src/Avalonia.Base/Rendering/ZIndexComparer.cs b/src/Avalonia.Base/Rendering/ZIndexComparer.cs index 4566b905c4..c9240668b4 100644 --- a/src/Avalonia.Base/Rendering/ZIndexComparer.cs +++ b/src/Avalonia.Base/Rendering/ZIndexComparer.cs @@ -4,11 +4,11 @@ using Avalonia.VisualTree; namespace Avalonia.Rendering { - public class ZIndexComparer : IComparer + public class ZIndexComparer : IComparer { public static readonly ZIndexComparer Instance = new ZIndexComparer(); - public static readonly Comparison ComparisonInstance = Instance.Compare; + public static readonly Comparison ComparisonInstance = Instance.Compare; - public int Compare(IVisual? x, IVisual? y) => (x?.ZIndex ?? 0).CompareTo(y?.ZIndex ?? 0); + public int Compare(Visual? x, Visual? y) => (x?.ZIndex ?? 0).CompareTo(y?.ZIndex ?? 0); } } diff --git a/src/Avalonia.Base/StyledElement.cs b/src/Avalonia.Base/StyledElement.cs index c72f398fd9..6fa2027e1e 100644 --- a/src/Avalonia.Base/StyledElement.cs +++ b/src/Avalonia.Base/StyledElement.cs @@ -3,6 +3,7 @@ using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; +using System.Diagnostics; using System.Linq; using Avalonia.Animation; using Avalonia.Collections; @@ -11,6 +12,7 @@ using Avalonia.Data; using Avalonia.Diagnostics; using Avalonia.Logging; using Avalonia.LogicalTree; +using Avalonia.PropertyStore; using Avalonia.Styling; namespace Avalonia @@ -23,7 +25,15 @@ namespace Avalonia /// - Implements to form part of a logical tree. /// - A collection of class strings for custom styling. /// - public class StyledElement : Animatable, IDataContextProvider, IStyledElement, ISetLogicalParent, ISetInheritanceParent + public class StyledElement : Animatable, + IDataContextProvider, + ILogical, + IResourceHost, + IStyleHost, + IStyleable, + ISetLogicalParent, + ISetInheritanceParent, + ISupportInitialize { /// /// Defines the property. @@ -43,14 +53,14 @@ namespace Avalonia /// /// Defines the property. /// - public static readonly DirectProperty ParentProperty = - AvaloniaProperty.RegisterDirect(nameof(Parent), o => o.Parent); + public static readonly DirectProperty ParentProperty = + AvaloniaProperty.RegisterDirect(nameof(Parent), o => o.Parent); /// /// Defines the property. /// - public static readonly DirectProperty TemplatedParentProperty = - AvaloniaProperty.RegisterDirect( + public static readonly DirectProperty TemplatedParentProperty = + AvaloniaProperty.RegisterDirect( nameof(TemplatedParent), o => o.TemplatedParent, (o ,v) => o.TemplatedParent = v); @@ -69,10 +79,10 @@ namespace Avalonia private IAvaloniaList? _logicalChildren; private IResourceDictionary? _resources; private Styles? _styles; - private bool _styled; - private ITemplatedControl? _templatedParent; + private bool _stylesApplied; + private bool _themeApplied; + private AvaloniaObject? _templatedParent; private bool _dataContextUpdating; - private bool _hasPromotedTheme; private ControlTheme? _implicitTheme; /// @@ -141,7 +151,7 @@ namespace Avalonia set { - if (_styled) + if (_stylesApplied) { throw new InvalidOperationException("Cannot set Name : styled element already styled."); } @@ -231,7 +241,7 @@ namespace Avalonia /// /// Gets the styled element whose lookless template this styled element is part of. /// - public ITemplatedControl? TemplatedParent + public AvaloniaObject? TemplatedParent { get => _templatedParent; internal set => SetAndRaise(TemplatedParentProperty, ref _templatedParent, value); @@ -249,7 +259,7 @@ namespace Avalonia /// /// Gets the styled element's logical children. /// - protected IAvaloniaList LogicalChildren + protected internal IAvaloniaList LogicalChildren { get { @@ -282,7 +292,7 @@ namespace Avalonia /// /// Gets the styled element's logical parent. /// - public IStyledElement? Parent { get; private set; } + public StyledElement? Parent { get; private set; } /// /// Gets the styled element's logical parent. @@ -353,38 +363,33 @@ namespace Avalonia /// public bool ApplyStyling() { - if (_initCount == 0 && !_styled) + if (_initCount == 0 && (!_stylesApplied || !_themeApplied)) { - var hasPromotedTheme = _hasPromotedTheme; - GetValueStore().BeginStyling(); try { - ApplyControlTheme(); - ApplyStyles(this); + if (!_themeApplied) + { + ApplyControlTheme(); + _themeApplied = true; + } + + if (!_stylesApplied) + { + ApplyStyles(this); + _stylesApplied = true; + } } finally { - _styled = true; GetValueStore().EndStyling(); } - - if (hasPromotedTheme) - { - _hasPromotedTheme = false; - ClearValue(ThemeProperty); - } } - return _styled; + return _stylesApplied; } - /// - /// Detaches all styles from the element and queues a restyle. - /// - protected virtual void InvalidateStyles() => DetachStyles(); - protected void InitializeIfNeeded() { if (_initCount == 0 && !IsInitialized) @@ -454,7 +459,7 @@ namespace Avalonia InheritanceParent = parent as AvaloniaObject; } - Parent = (IStyledElement?)parent; + Parent = (StyledElement?)parent; if (_logicalRoot != null) { @@ -485,8 +490,8 @@ namespace Avalonia #nullable disable RaisePropertyChanged( ParentProperty, - new Optional(old), - new BindingValue(Parent), + new Optional(old), + new BindingValue(Parent), BindingPriority.LocalValue); #nullable enable } @@ -496,27 +501,21 @@ namespace Avalonia /// Sets the styled element's inheritance parent. /// /// The parent. - void ISetInheritanceParent.SetParent(IAvaloniaObject? parent) + void ISetInheritanceParent.SetParent(AvaloniaObject? parent) { - InheritanceParent = parent switch - { - AvaloniaObject ao => ao, - null => null, - _ => throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported.") - }; + InheritanceParent = parent; } - void IStyleable.DetachStyles() => DetachStyles(); - void IStyleHost.StylesAdded(IReadOnlyList styles) { - InvalidateStylesOnThisAndDescendents(); + if (HasSettersOrAnimations(styles)) + InvalidateStyles(recurse: true); } void IStyleHost.StylesRemoved(IReadOnlyList styles) { - var allStyles = RecurseStyles(styles); - DetachStylesFromThisAndDescendents(allStyles); + if (FlattenStyles(styles) is { } allStyles) + DetachStyles(allStyles); } protected virtual void LogicalChildrenCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) @@ -615,31 +614,25 @@ namespace Avalonia if (change.Property == ThemeProperty) { - var (oldValue, newValue) = change.GetOldAndNewValue(); - - // Changing the theme detaches all styles, meaning that if the theme property was - // set via a style, it will get cleared! To work around this, if the value was - // applied at less than local value priority then promote the value to local value - // priority until styling is re-applied. - if (change.Priority > BindingPriority.LocalValue) - { - Theme = newValue; - _hasPromotedTheme = true; - } - else if (_hasPromotedTheme && change.Priority == BindingPriority.LocalValue) - { - _hasPromotedTheme = false; - } - - InvalidateStyles(); - - if (oldValue is not null) - DetachControlThemeFromTemplateChildren(oldValue); + OnControlThemeChanged(); + _themeApplied = false; } } - internal virtual void DetachControlThemeFromTemplateChildren(ControlTheme theme) + private protected virtual void OnControlThemeChanged() { + var values = GetValueStore(); + values.BeginStyling(); + try { values.RemoveFrames(FrameType.Theme); } + finally { values.EndStyling(); } + } + + internal virtual void OnTemplatedParentControlThemeChanged() + { + var values = GetValueStore(); + values.BeginStyling(); + try { values.RemoveFrames(FrameType.TemplatedParentTheme); } + finally { values.EndStyling(); } } internal ControlTheme? GetEffectiveTheme() @@ -667,7 +660,24 @@ namespace Avalonia return null; } - private static void DataContextNotifying(IAvaloniaObject o, bool updateStarted) + internal virtual void InvalidateStyles(bool recurse) + { + var values = GetValueStore(); + values.BeginStyling(); + try { values.RemoveFrames(FrameType.Style); } + finally { values.EndStyling(); } + + _stylesApplied = false; + + if (recurse && GetInheritanceChildren() is { } children) + { + var childCount = children.Count; + for (var i = 0; i < childCount; ++i) + (children[i] as StyledElement)?.InvalidateStyles(recurse); + } + } + + private static void DataContextNotifying(AvaloniaObject o, bool updateStarted) { if (o is StyledElement element) { @@ -736,26 +746,28 @@ namespace Avalonia var theme = GetEffectiveTheme(); if (theme is not null) - ApplyControlTheme(theme); + ApplyControlTheme(theme, FrameType.Theme); if (TemplatedParent is StyledElement styleableParent && styleableParent.GetEffectiveTheme() is { } parentTheme) { - ApplyControlTheme(parentTheme); + ApplyControlTheme(parentTheme, FrameType.TemplatedParentTheme); } } - private void ApplyControlTheme(ControlTheme theme) + private void ApplyControlTheme(ControlTheme theme, FrameType type) { + Debug.Assert(type is FrameType.Theme or FrameType.TemplatedParentTheme); + if (theme.BasedOn is ControlTheme basedOn) - ApplyControlTheme(basedOn); + ApplyControlTheme(basedOn, type); - theme.TryAttach(this, null); + theme.TryAttach(this, type); if (theme.HasChildren) { foreach (var child in theme.Children) - ApplyStyle(child, null); + ApplyStyle(child, null, type); } } @@ -768,17 +780,17 @@ namespace Avalonia if (host.IsStylesInitialized) { foreach (var style in host.Styles) - ApplyStyle(style, host); + ApplyStyle(style, host, FrameType.Style); } } - private void ApplyStyle(IStyle style, IStyleHost? host) + private void ApplyStyle(IStyle style, IStyleHost? host, FrameType type) { if (style is Style s) - s.TryAttach(this, host); + s.TryAttach(this, host, type); foreach (var child in style.Children) - ApplyStyle(child, host); + ApplyStyle(child, host, type); } private void OnAttachedToLogicalTreeCore(LogicalTreeAttachmentEventArgs e) @@ -824,7 +836,7 @@ namespace Avalonia { _logicalRoot = null; _implicitTheme = null; - DetachStyles(); + InvalidateStyles(recurse: false); OnDetachedFromLogicalTree(e); DetachedFromLogicalTree?.Invoke(this, e); @@ -886,70 +898,81 @@ namespace Avalonia } } - private void DetachStyles(IReadOnlyList? styles = null) + private void DetachStyles(IReadOnlyList - - -"; - - using (StyledWindow(assets: ("test:style.xaml", styleXaml))) - { - var xaml = @" +"), + new RuntimeXamlLoaderDocument(@" - + -"; - - var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); +") + }; + + using (StyledWindow()) + { + var compiled = AvaloniaRuntimeXamlLoader.LoadGroup(documents); + var window = Assert.IsType(compiled[1]); var border = window.FindControl("border"); var brush = (ISolidColorBrush)border.Background; @@ -284,13 +286,14 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions [Fact] public void DynamicResource_Can_Be_Assigned_To_Property_In_ControlTemplate_In_Styles_File() { - var styleXaml = @" + var documents = new[] + { + new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Style.xaml"), @" #ff506070 - -"; - - using (StyledWindow(assets: ("test:style.xaml", styleXaml))) - { - var xaml = @" +"), + new RuntimeXamlLoaderDocument(@" - +