106 changed files with 2746 additions and 1502 deletions
@ -0,0 +1,10 @@ |
|||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
|||
<ItemGroup> |
|||
<ProjectReference |
|||
Include="$(MSBuildThisFileDirectory)/../src/Avalonia.SourceGenerator/Avalonia.SourceGenerator.csproj" |
|||
OutputItemType="Analyzer" |
|||
ReferenceOutputAssembly="false" |
|||
PrivateAssets="all" /> |
|||
<Compile Include="$(MSBuildThisFileDirectory)/../src/Shared/SourceGeneratorAttributes.cs" /> |
|||
</ItemGroup> |
|||
</Project> |
|||
@ -1,7 +0,0 @@ |
|||
<Directives> |
|||
<Application> |
|||
<Assembly Name="ControlCatalog" Dynamic="Required All"></Assembly> |
|||
<Assembly Name="Avalonia.Themes.Default" Dynamic="Required All"></Assembly> |
|||
<Assembly Name="Avalonia.Themes.Fluent" Dynamic="Required All"></Assembly> |
|||
</Application> |
|||
</Directives> |
|||
@ -1,79 +0,0 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
<PropertyGroup> |
|||
<TargetFramework>net6.0-android</TargetFramework> |
|||
<SupportedOSPlatformVersion>21</SupportedOSPlatformVersion> |
|||
<OutputType>Exe</OutputType> |
|||
<Nullable>enable</Nullable> |
|||
<ApplicationId>com.Avalonia.AndroidTestApplication</ApplicationId> |
|||
<ApplicationVersion>1</ApplicationVersion> |
|||
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion> |
|||
<AndroidPackageFormat>apk</AndroidPackageFormat> |
|||
<MSBuildEnableWorkloadResolver>true</MSBuildEnableWorkloadResolver> |
|||
<DebugType>portable</DebugType> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<AndroidResource Include="..\..\..\build\Assets\Icon.png"> |
|||
<Link>Resources\drawable\Icon.png</Link> |
|||
</AndroidResource> |
|||
</ItemGroup> |
|||
|
|||
<PropertyGroup Condition="'$(Configuration)'=='Release' and '$(TF_BUILD)' == ''"> |
|||
<DebugSymbols>True</DebugSymbols> |
|||
<RunAOTCompilation>True</RunAOTCompilation> |
|||
<EnableLLVM>True</EnableLLVM> |
|||
<AndroidEnableProfiledAot>True</AndroidEnableProfiledAot> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<None Remove="Assets\AboutAssets.txt" /> |
|||
</ItemGroup> |
|||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> |
|||
<BundleAssemblies>True</BundleAssemblies> |
|||
</PropertyGroup> |
|||
|
|||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> |
|||
<BundleAssemblies>True</BundleAssemblies> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\..\packages\Avalonia\Avalonia.csproj" /> |
|||
<ProjectReference Include="..\Avalonia.Android\Avalonia.Android.csproj" /> |
|||
</ItemGroup> |
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\..\src\Android\Avalonia.Android\Avalonia.Android.csproj"> |
|||
<Project>{7b92af71-6287-4693-9dcb-bd5b6e927e23}</Project> |
|||
<Name>Avalonia.Android</Name> |
|||
</ProjectReference> |
|||
<ProjectReference Include="..\..\..\src\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj"> |
|||
<Project>{3e53a01a-b331-47f3-b828-4a5717e77a24}</Project> |
|||
<Name>Avalonia.Markup.Xaml</Name> |
|||
</ProjectReference> |
|||
<ProjectReference Include="..\..\..\src\Avalonia.Base\Avalonia.Base.csproj"> |
|||
<Project>{b09b78d8-9b26-48b0-9149-d64a2f120f3f}</Project> |
|||
<Name>Avalonia.Base</Name> |
|||
</ProjectReference> |
|||
<ProjectReference Include="..\..\..\src\Avalonia.Controls\Avalonia.Controls.csproj"> |
|||
<Project>{d2221c82-4a25-4583-9b43-d791e3f6820c}</Project> |
|||
<Name>Avalonia.Controls</Name> |
|||
</ProjectReference> |
|||
<ProjectReference Include="..\..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj"> |
|||
<Project>{7062ae20-5dcc-4442-9645-8195bdece63e}</Project> |
|||
<Name>Avalonia.Diagnostics</Name> |
|||
</ProjectReference> |
|||
<ProjectReference Include="..\..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj"> |
|||
<Project>{3e10a5fa-e8da-48b1-ad44-6a5b6cb7750f}</Project> |
|||
<Name>Avalonia.Themes.Default</Name> |
|||
</ProjectReference> |
|||
<ProjectReference Include="..\..\Skia\Avalonia.Skia\Avalonia.Skia.csproj"> |
|||
<Project>{7d2d3083-71dd-4cc9-8907-39a0d86fb322}</Project> |
|||
<Name>Avalonia.Skia</Name> |
|||
</ProjectReference> |
|||
</ItemGroup> |
|||
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" /> |
|||
<Import Project="..\..\..\build\Base.props" /> |
|||
<Import Project="..\..\..\build\Rx.props" /> |
|||
<Import Project="..\..\..\build\System.Memory.props" /> |
|||
<Import Project="..\..\..\build\AndroidWorkarounds.props" /> |
|||
<Import Project="..\..\..\build\LegacyProject.targets" /> |
|||
</Project> |
|||
@ -1,104 +0,0 @@ |
|||
using System; |
|||
using Android.App; |
|||
using Android.Content.PM; |
|||
using Avalonia.Android; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Controls.ApplicationLifetimes; |
|||
using Avalonia.Input.TextInput; |
|||
using Avalonia.Markup.Xaml; |
|||
using Avalonia.Media; |
|||
using Avalonia.Styling; |
|||
using Avalonia.Themes.Default; |
|||
|
|||
namespace Avalonia.AndroidTestApplication |
|||
{ |
|||
[Activity(Label = "Main", |
|||
MainLauncher = true, |
|||
Icon = "@drawable/icon", |
|||
Theme = "@style/Theme.AppCompat.NoActionBar", |
|||
ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, |
|||
LaunchMode = LaunchMode.SingleInstance/*, |
|||
ScreenOrientation = ScreenOrientation.Landscape*/)] |
|||
public class MainActivity : AvaloniaActivity<App> |
|||
{ |
|||
protected override AppBuilder CustomizeAppBuilder(AppBuilder builder) |
|||
{ |
|||
return base.CustomizeAppBuilder(builder); |
|||
} |
|||
} |
|||
|
|||
public class App : Application |
|||
{ |
|||
public override void Initialize() |
|||
{ |
|||
Styles.Add(new SimpleTheme(new Uri("avares://Avalonia.AndroidTestApplication"))); |
|||
} |
|||
|
|||
public override void OnFrameworkInitializationCompleted() |
|||
{ |
|||
if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewLifetime) |
|||
{ |
|||
singleViewLifetime.MainView = CreateSimpleWindow(); |
|||
} |
|||
|
|||
base.OnFrameworkInitializationCompleted(); |
|||
} |
|||
|
|||
// This provides a simple UI tree for testing input handling, drawing, etc
|
|||
public static ContentControl CreateSimpleWindow() |
|||
{ |
|||
ContentControl window = new ContentControl() |
|||
{ |
|||
Background = Brushes.Red, |
|||
Content = new StackPanel |
|||
{ |
|||
Margin = new Thickness(30), |
|||
Background = Brushes.Yellow, |
|||
Children = |
|||
{ |
|||
new TextBlock |
|||
{ |
|||
Text = "TEXT BLOCK", |
|||
Width = 300, |
|||
Height = 40, |
|||
Background = Brushes.White, |
|||
Foreground = Brushes.Black |
|||
}, |
|||
|
|||
new Button |
|||
{ |
|||
Content = "BUTTON", |
|||
Width = 150, |
|||
Height = 40, |
|||
Background = Brushes.LightGreen, |
|||
Foreground = Brushes.Black |
|||
}, |
|||
|
|||
CreateTextBox(TextInputContentType.Normal), |
|||
CreateTextBox(TextInputContentType.Password), |
|||
CreateTextBox(TextInputContentType.Email), |
|||
CreateTextBox(TextInputContentType.Url), |
|||
CreateTextBox(TextInputContentType.Digits), |
|||
CreateTextBox(TextInputContentType.Number), |
|||
} |
|||
} |
|||
}; |
|||
|
|||
return window; |
|||
} |
|||
|
|||
private static TextBox CreateTextBox(TextInputContentType contentType) |
|||
{ |
|||
var textBox = new TextBox() |
|||
{ |
|||
Margin = new Thickness(20, 10), |
|||
Watermark = contentType.ToString(), |
|||
BorderThickness = new Thickness(3), |
|||
FontSize = 20, |
|||
[TextInputOptions.ContentTypeProperty] = contentType |
|||
}; |
|||
|
|||
return textBox; |
|||
} |
|||
} |
|||
} |
|||
@ -1,4 +0,0 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> |
|||
<application android:label="Avalonia.AndroidTestApplication" android:icon="@drawable/Icon" android:hardwareAccelerated="true"></application> |
|||
</manifest> |
|||
@ -1,50 +0,0 @@ |
|||
Images, layout descriptions, binary blobs and string dictionaries can be included |
|||
in your application as resource files. Various Android APIs are designed to |
|||
operate on the resource IDs instead of dealing with images, strings or binary blobs |
|||
directly. |
|||
|
|||
For example, a sample Android app that contains a user interface layout (main.xml), |
|||
an internationalization string table (strings.xml) and some icons (drawable-XXX/icon.png) |
|||
would keep its resources in the "Resources" directory of the application: |
|||
|
|||
Resources/ |
|||
drawable-hdpi/ |
|||
icon.png |
|||
|
|||
drawable-ldpi/ |
|||
icon.png |
|||
|
|||
drawable-mdpi/ |
|||
icon.png |
|||
|
|||
layout/ |
|||
main.xml |
|||
|
|||
values/ |
|||
strings.xml |
|||
|
|||
In order to get the build system to recognize Android resources, set the build action to |
|||
"AndroidResource". The native Android APIs do not operate directly with filenames, but |
|||
instead operate on resource IDs. When you compile an Android application that uses resources, |
|||
the build system will package the resources for distribution and generate a class called |
|||
"Resource" that contains the tokens for each one of the resources included. For example, |
|||
for the above Resources layout, this is what the Resource class would expose: |
|||
|
|||
public class Resource { |
|||
public class drawable { |
|||
public const int icon = 0x123; |
|||
} |
|||
|
|||
public class layout { |
|||
public const int main = 0x456; |
|||
} |
|||
|
|||
public class strings { |
|||
public const int first_string = 0xabc; |
|||
public const int second_string = 0xbcd; |
|||
} |
|||
} |
|||
|
|||
You would then use R.drawable.icon to reference the drawable/icon.png file, or Resource.layout.main |
|||
to reference the layout/main.xml file, or Resource.strings.first_string to reference the first |
|||
string in the dictionary file values/strings.xml. |
|||
|
Before Width: | Height: | Size: 4.0 KiB |
@ -1,6 +0,0 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
|
|||
<resources> |
|||
<string name="Hello">Hello World, Click Me!</string> |
|||
<string name="ApplicationName">Avalonia.AndroidTestApplication</string> |
|||
</resources> |
|||
@ -1,59 +1,4 @@ |
|||
Compat issues with assembly Avalonia.Base: |
|||
MembersMustExist : Member 'public void Avalonia.AvaloniaObject.CoerceValue<T>(Avalonia.StyledPropertyBase<T>)' does not exist in the implementation but it does exist in the contract. |
|||
MembersMustExist : Member 'protected Avalonia.IAvaloniaObject Avalonia.AvaloniaObject.InheritanceParent.get()' does not exist in the implementation but it does exist in the contract. |
|||
MembersMustExist : Member 'protected void Avalonia.AvaloniaObject.InheritanceParent.set(Avalonia.IAvaloniaObject)' does not exist in the implementation but it does exist in the contract. |
|||
MembersMustExist : Member 'protected void Avalonia.AvaloniaObject.OnPropertyChanged<T>(Avalonia.AvaloniaPropertyChangedEventArgs<T>)' does not exist in the implementation but it does exist in the contract. |
|||
MembersMustExist : Member 'protected void Avalonia.AvaloniaObject.OnPropertyChangedCore<T>(Avalonia.AvaloniaPropertyChangedEventArgs<T>)' does not exist in the implementation but it does exist in the contract. |
|||
MembersMustExist : Member 'public void Avalonia.AvaloniaObject.SetValue(Avalonia.AvaloniaProperty, System.Object, Avalonia.Data.BindingPriority)' does not exist in the implementation but it does exist in the contract. |
|||
MembersMustExist : Member 'public System.IDisposable Avalonia.AvaloniaObjectExtensions.Bind(Avalonia.IAvaloniaObject, Avalonia.AvaloniaProperty, System.IObservable<Avalonia.Data.BindingValue<System.Object>>, Avalonia.Data.BindingPriority)' does not exist in the implementation but it does exist in the contract. |
|||
MembersMustExist : Member 'public System.IDisposable Avalonia.AvaloniaObjectExtensions.Bind(Avalonia.IAvaloniaObject, Avalonia.AvaloniaProperty, System.IObservable<System.Object>, Avalonia.Data.BindingPriority)' does not exist in the implementation but it does exist in the contract. |
|||
MembersMustExist : Member 'public void Avalonia.AvaloniaObjectExtensions.ClearValue(Avalonia.IAvaloniaObject, Avalonia.AvaloniaProperty)' does not exist in the implementation but it does exist in the contract. |
|||
MembersMustExist : Member 'public void Avalonia.AvaloniaObjectExtensions.ClearValue<T>(Avalonia.IAvaloniaObject, Avalonia.AvaloniaProperty<T>)' does not exist in the implementation but it does exist in the contract. |
|||
MembersMustExist : Member 'public System.Object Avalonia.AvaloniaObjectExtensions.GetValue(Avalonia.IAvaloniaObject, Avalonia.AvaloniaProperty)' does not exist in the implementation but it does exist in the contract. |
|||
MembersMustExist : Member 'public System.IDisposable Avalonia.AvaloniaObjectExtensions.SetValue(Avalonia.IAvaloniaObject, Avalonia.AvaloniaProperty, System.Object, Avalonia.Data.BindingPriority)' does not exist in the implementation but it does exist in the contract. |
|||
MembersMustExist : Member 'public System.IDisposable Avalonia.AvaloniaObjectExtensions.SetValue<T>(Avalonia.IAvaloniaObject, Avalonia.AvaloniaProperty<T>, T, Avalonia.Data.BindingPriority)' does not exist in the implementation but it does exist in the contract. |
|||
CannotAddAbstractMembers : Member 'internal System.IDisposable Avalonia.AvaloniaProperty.RouteBind(Avalonia.AvaloniaObject, System.IObservable<Avalonia.Data.BindingValue<System.Object>>, Avalonia.Data.BindingPriority)' is abstract in the implementation but is missing in the contract. |
|||
MembersMustExist : Member 'internal System.IDisposable Avalonia.AvaloniaProperty.RouteBind(Avalonia.IAvaloniaObject, System.IObservable<Avalonia.Data.BindingValue<System.Object>>, Avalonia.Data.BindingPriority)' does not exist in the implementation but it does exist in the contract. |
|||
CannotAddAbstractMembers : Member 'internal void Avalonia.AvaloniaProperty.RouteClearValue(Avalonia.AvaloniaObject)' is abstract in the implementation but is missing in the contract. |
|||
MembersMustExist : Member 'internal void Avalonia.AvaloniaProperty.RouteClearValue(Avalonia.IAvaloniaObject)' does not exist in the implementation but it does exist in the contract. |
|||
CannotAddAbstractMembers : Member 'internal System.Object Avalonia.AvaloniaProperty.RouteGetBaseValue(Avalonia.AvaloniaObject, Avalonia.Data.BindingPriority)' is abstract in the implementation but is missing in the contract. |
|||
MembersMustExist : Member 'internal System.Object Avalonia.AvaloniaProperty.RouteGetBaseValue(Avalonia.IAvaloniaObject, Avalonia.Data.BindingPriority)' does not exist in the implementation but it does exist in the contract. |
|||
CannotAddAbstractMembers : Member 'internal System.Object Avalonia.AvaloniaProperty.RouteGetValue(Avalonia.AvaloniaObject)' is abstract in the implementation but is missing in the contract. |
|||
MembersMustExist : Member 'internal System.Object Avalonia.AvaloniaProperty.RouteGetValue(Avalonia.IAvaloniaObject)' does not exist in the implementation but it does exist in the contract. |
|||
CannotAddAbstractMembers : Member 'internal void Avalonia.AvaloniaProperty.RouteInheritanceParentChanged(Avalonia.AvaloniaObject, Avalonia.AvaloniaObject)' is abstract in the implementation but is missing in the contract. |
|||
MembersMustExist : Member 'internal void Avalonia.AvaloniaProperty.RouteInheritanceParentChanged(Avalonia.AvaloniaObject, Avalonia.IAvaloniaObject)' does not exist in the implementation but it does exist in the contract. |
|||
CannotAddAbstractMembers : Member 'internal System.IDisposable Avalonia.AvaloniaProperty.RouteSetValue(Avalonia.AvaloniaObject, System.Object, Avalonia.Data.BindingPriority)' is abstract in the implementation but is missing in the contract. |
|||
MembersMustExist : Member 'internal System.IDisposable Avalonia.AvaloniaProperty.RouteSetValue(Avalonia.IAvaloniaObject, System.Object, Avalonia.Data.BindingPriority)' does not exist in the implementation but it does exist in the contract. |
|||
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.IAvaloniaObject.AddInheritanceChild(Avalonia.IAvaloniaObject)' is present in the contract but not in the implementation. |
|||
MembersMustExist : Member 'public void Avalonia.IAvaloniaObject.AddInheritanceChild(Avalonia.IAvaloniaObject)' does not exist in the implementation but it does exist in the contract. |
|||
InterfacesShouldHaveSameMembers : Interface member 'public System.IDisposable Avalonia.IAvaloniaObject.Bind(Avalonia.AvaloniaProperty, System.IObservable<System.Object>, Avalonia.Data.BindingPriority)' is present in the implementation but not in the contract. |
|||
InterfacesShouldHaveSameMembers : Interface member 'public System.IDisposable Avalonia.IAvaloniaObject.Bind<T>(Avalonia.DirectPropertyBase<T>, System.IObservable<Avalonia.Data.BindingValue<T>>)' is present in the contract but not in the implementation. |
|||
MembersMustExist : Member 'public System.IDisposable Avalonia.IAvaloniaObject.Bind<T>(Avalonia.DirectPropertyBase<T>, System.IObservable<Avalonia.Data.BindingValue<T>>)' does not exist in the implementation but it does exist in the contract. |
|||
InterfacesShouldHaveSameMembers : Interface member 'public System.IDisposable Avalonia.IAvaloniaObject.Bind<T>(Avalonia.StyledPropertyBase<T>, System.IObservable<Avalonia.Data.BindingValue<T>>, Avalonia.Data.BindingPriority)' is present in the contract but not in the implementation. |
|||
MembersMustExist : Member 'public System.IDisposable Avalonia.IAvaloniaObject.Bind<T>(Avalonia.StyledPropertyBase<T>, System.IObservable<Avalonia.Data.BindingValue<T>>, Avalonia.Data.BindingPriority)' does not exist in the implementation but it does exist in the contract. |
|||
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.IAvaloniaObject.ClearValue(Avalonia.AvaloniaProperty)' is present in the implementation but not in the contract. |
|||
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.IAvaloniaObject.ClearValue<T>(Avalonia.DirectPropertyBase<T>)' is present in the contract but not in the implementation. |
|||
MembersMustExist : Member 'public void Avalonia.IAvaloniaObject.ClearValue<T>(Avalonia.DirectPropertyBase<T>)' does not exist in the implementation but it does exist in the contract. |
|||
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.IAvaloniaObject.ClearValue<T>(Avalonia.StyledPropertyBase<T>)' is present in the contract but not in the implementation. |
|||
MembersMustExist : Member 'public void Avalonia.IAvaloniaObject.ClearValue<T>(Avalonia.StyledPropertyBase<T>)' does not exist in the implementation but it does exist in the contract. |
|||
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.IAvaloniaObject.CoerceValue(Avalonia.AvaloniaProperty)' is present in the implementation but not in the contract. |
|||
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.IAvaloniaObject.CoerceValue<T>(Avalonia.StyledPropertyBase<T>)' is present in the contract but not in the implementation. |
|||
MembersMustExist : Member 'public void Avalonia.IAvaloniaObject.CoerceValue<T>(Avalonia.StyledPropertyBase<T>)' does not exist in the implementation but it does exist in the contract. |
|||
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Data.Optional<T> Avalonia.IAvaloniaObject.GetBaseValue<T>(Avalonia.StyledPropertyBase<T>, Avalonia.Data.BindingPriority)' is present in the contract but not in the implementation. |
|||
MembersMustExist : Member 'public Avalonia.Data.Optional<T> Avalonia.IAvaloniaObject.GetBaseValue<T>(Avalonia.StyledPropertyBase<T>, Avalonia.Data.BindingPriority)' does not exist in the implementation but it does exist in the contract. |
|||
InterfacesShouldHaveSameMembers : Interface member 'public System.Object Avalonia.IAvaloniaObject.GetValue(Avalonia.AvaloniaProperty)' is present in the implementation but not in the contract. |
|||
InterfacesShouldHaveSameMembers : Interface member 'public T Avalonia.IAvaloniaObject.GetValue<T>(Avalonia.DirectPropertyBase<T>)' is present in the contract but not in the implementation. |
|||
MembersMustExist : Member 'public T Avalonia.IAvaloniaObject.GetValue<T>(Avalonia.DirectPropertyBase<T>)' does not exist in the implementation but it does exist in the contract. |
|||
InterfacesShouldHaveSameMembers : Interface member 'public T Avalonia.IAvaloniaObject.GetValue<T>(Avalonia.StyledPropertyBase<T>)' is present in the contract but not in the implementation. |
|||
MembersMustExist : Member 'public T Avalonia.IAvaloniaObject.GetValue<T>(Avalonia.StyledPropertyBase<T>)' does not exist in the implementation but it does exist in the contract. |
|||
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.IAvaloniaObject.InheritedPropertyChanged<T>(Avalonia.AvaloniaProperty<T>, Avalonia.Data.Optional<T>, Avalonia.Data.Optional<T>)' is present in the contract but not in the implementation. |
|||
MembersMustExist : Member 'public void Avalonia.IAvaloniaObject.InheritedPropertyChanged<T>(Avalonia.AvaloniaProperty<T>, Avalonia.Data.Optional<T>, Avalonia.Data.Optional<T>)' does not exist in the implementation but it does exist in the contract. |
|||
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.IAvaloniaObject.RemoveInheritanceChild(Avalonia.IAvaloniaObject)' is present in the contract but not in the implementation. |
|||
MembersMustExist : Member 'public void Avalonia.IAvaloniaObject.RemoveInheritanceChild(Avalonia.IAvaloniaObject)' does not exist in the implementation but it does exist in the contract. |
|||
InterfacesShouldHaveSameMembers : Interface member 'public System.IDisposable Avalonia.IAvaloniaObject.SetValue(Avalonia.AvaloniaProperty, System.Object, Avalonia.Data.BindingPriority)' is present in the implementation but not in the contract. |
|||
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.IAvaloniaObject.SetValue<T>(Avalonia.DirectPropertyBase<T>, T)' is present in the contract but not in the implementation. |
|||
MembersMustExist : Member 'public void Avalonia.IAvaloniaObject.SetValue<T>(Avalonia.DirectPropertyBase<T>, T)' does not exist in the implementation but it does exist in the contract. |
|||
InterfacesShouldHaveSameMembers : Interface member 'public System.IDisposable Avalonia.IAvaloniaObject.SetValue<T>(Avalonia.StyledPropertyBase<T>, T, Avalonia.Data.BindingPriority)' is present in the contract but not in the implementation. |
|||
MembersMustExist : Member 'public System.IDisposable Avalonia.IAvaloniaObject.SetValue<T>(Avalonia.StyledPropertyBase<T>, T, Avalonia.Data.BindingPriority)' does not exist in the implementation but it does exist in the contract. |
|||
MembersMustExist : Member 'public System.Int32 System.Int32 Avalonia.Threading.DispatcherPriority.value__' does not exist in the implementation but it does exist in the contract. |
|||
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Threading.IDispatcher.Post<T>(System.Action<T>, T, Avalonia.Threading.DispatcherPriority)' is present in the implementation but not in the contract. |
|||
Total Issues: 57 |
|||
Total Issues: 2 |
|||
|
|||
@ -1,17 +1,31 @@ |
|||
using System; |
|||
using Avalonia.VisualTree; |
|||
using Avalonia.Input.Raw; |
|||
|
|||
namespace Avalonia.Input |
|||
{ |
|||
public interface IPointerDevice : IInputDevice |
|||
{ |
|||
/// <inheritdoc cref="IPointer.Captured" />
|
|||
[Obsolete("Use IPointer")] |
|||
IInputElement? Captured { get; } |
|||
|
|||
|
|||
/// <inheritdoc cref="IPointer.Capture(IInputElement?)" />
|
|||
[Obsolete("Use IPointer")] |
|||
void Capture(IInputElement? control); |
|||
|
|||
/// <inheritdoc cref="PointerEventArgs.GetPosition(IVisual?)" />
|
|||
[Obsolete("Use PointerEventArgs.GetPosition")] |
|||
Point GetPosition(IVisual relativeTo); |
|||
|
|||
/// <summary>
|
|||
/// Gets a pointer for specific event args.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// If pointer doesn't exist or wasn't yet created this method will return null.
|
|||
/// </remarks>
|
|||
/// <param name="ev">Raw pointer event args associated with the pointer.</param>
|
|||
/// <returns>The pointer.</returns>
|
|||
IPointer? TryGetPointer(RawPointerEventArgs ev); |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,209 @@ |
|||
using System; |
|||
using Avalonia.Input.Raw; |
|||
|
|||
namespace Avalonia.Input |
|||
{ |
|||
internal class PointerOverPreProcessor : IObserver<RawInputEventArgs> |
|||
{ |
|||
private IPointerDevice? _lastActivePointerDevice; |
|||
private (IPointer pointer, PixelPoint position)? _lastPointer; |
|||
|
|||
private readonly IInputRoot _inputRoot; |
|||
|
|||
public PointerOverPreProcessor(IInputRoot inputRoot) |
|||
{ |
|||
_inputRoot = inputRoot ?? throw new ArgumentNullException(nameof(inputRoot)); |
|||
} |
|||
|
|||
public void OnCompleted() |
|||
{ |
|||
ClearPointerOver(); |
|||
} |
|||
|
|||
public void OnError(Exception error) |
|||
{ |
|||
} |
|||
|
|||
public void OnNext(RawInputEventArgs value) |
|||
{ |
|||
if (value is RawPointerEventArgs args |
|||
&& args.Root == _inputRoot |
|||
&& value.Device is IPointerDevice pointerDevice) |
|||
{ |
|||
if (pointerDevice != _lastActivePointerDevice) |
|||
{ |
|||
ClearPointerOver(); |
|||
|
|||
// Set last active device before processing input, because ClearPointerOver might be called and clear last device.
|
|||
_lastActivePointerDevice = pointerDevice; |
|||
} |
|||
|
|||
if (args.Type is RawPointerEventType.LeaveWindow or RawPointerEventType.NonClientLeftButtonDown |
|||
&& _lastPointer is (var lastPointer, var lastPosition)) |
|||
{ |
|||
_lastPointer = null; |
|||
ClearPointerOver(lastPointer, args.Root, 0, args.Root.PointToClient(lastPosition), |
|||
new PointerPointProperties(args.InputModifiers, args.Type.ToUpdateKind()), |
|||
args.InputModifiers.ToKeyModifiers()); |
|||
} |
|||
else if (pointerDevice.TryGetPointer(args) is IPointer pointer |
|||
&& pointer.Type != PointerType.Touch) |
|||
{ |
|||
var element = pointer.Captured ?? args.InputHitTestResult; |
|||
|
|||
SetPointerOver(pointer, args.Root, element, args.Timestamp, args.Position, |
|||
new PointerPointProperties(args.InputModifiers, args.Type.ToUpdateKind()), |
|||
args.InputModifiers.ToKeyModifiers()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public void SceneInvalidated(Rect dirtyRect) |
|||
{ |
|||
if (_lastPointer is (var pointer, var position)) |
|||
{ |
|||
var clientPoint = _inputRoot.PointToClient(position); |
|||
|
|||
if (dirtyRect.Contains(clientPoint)) |
|||
{ |
|||
SetPointerOver(pointer, _inputRoot, _inputRoot.InputHitTest(clientPoint), 0, clientPoint, PointerPointProperties.None, KeyModifiers.None); |
|||
} |
|||
else if (!_inputRoot.Bounds.Contains(clientPoint)) |
|||
{ |
|||
ClearPointerOver(pointer, _inputRoot, 0, new Point(-1, -1), PointerPointProperties.None, KeyModifiers.None); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void ClearPointerOver() |
|||
{ |
|||
if (_lastPointer is (var pointer, var _)) |
|||
{ |
|||
ClearPointerOver(pointer, _inputRoot, 0, new Point(-1, -1), PointerPointProperties.None, KeyModifiers.None); |
|||
} |
|||
_lastPointer = null; |
|||
_lastActivePointerDevice = null; |
|||
} |
|||
|
|||
private void ClearPointerOver(IPointer pointer, IInputRoot root, |
|||
ulong timestamp, Point position, PointerPointProperties properties, KeyModifiers inputModifiers) |
|||
{ |
|||
var element = root.PointerOverElement; |
|||
if (element is null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
// Do not pass rootVisual, when we have unknown (negative) position,
|
|||
// so GetPosition won't return invalid values.
|
|||
var hasPosition = position.X >= 0 && position.Y >= 0; |
|||
var e = new PointerEventArgs(InputElement.PointerLeaveEvent, element, pointer, |
|||
hasPosition ? root : null, hasPosition ? position : default, |
|||
timestamp, properties, inputModifiers); |
|||
|
|||
if (element != null && !element.IsAttachedToVisualTree) |
|||
{ |
|||
// element has been removed from visual tree so do top down cleanup
|
|||
if (root.IsPointerOver) |
|||
{ |
|||
ClearChildrenPointerOver(e, root, true); |
|||
} |
|||
} |
|||
while (element != null) |
|||
{ |
|||
e.Source = element; |
|||
e.Handled = false; |
|||
element.RaiseEvent(e); |
|||
element = (IInputElement?)element.VisualParent; |
|||
} |
|||
|
|||
root.PointerOverElement = null; |
|||
_lastActivePointerDevice = null; |
|||
_lastPointer = null; |
|||
} |
|||
|
|||
private void ClearChildrenPointerOver(PointerEventArgs e, IInputElement element, bool clearRoot) |
|||
{ |
|||
foreach (IInputElement el in element.VisualChildren) |
|||
{ |
|||
if (el.IsPointerOver) |
|||
{ |
|||
ClearChildrenPointerOver(e, el, true); |
|||
break; |
|||
} |
|||
} |
|||
if (clearRoot) |
|||
{ |
|||
e.Source = element; |
|||
e.Handled = false; |
|||
element.RaiseEvent(e); |
|||
} |
|||
} |
|||
|
|||
private void SetPointerOver(IPointer pointer, IInputRoot root, IInputElement? element, |
|||
ulong timestamp, Point position, PointerPointProperties properties, KeyModifiers inputModifiers) |
|||
{ |
|||
var pointerOverElement = root.PointerOverElement; |
|||
|
|||
if (element != pointerOverElement) |
|||
{ |
|||
if (element != null) |
|||
{ |
|||
SetPointerOverToElement(pointer, root, element, timestamp, position, properties, inputModifiers); |
|||
} |
|||
else |
|||
{ |
|||
ClearPointerOver(pointer, root, timestamp, position, properties, inputModifiers); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void SetPointerOverToElement(IPointer pointer, IInputRoot root, IInputElement element, |
|||
ulong timestamp, Point position, PointerPointProperties properties, KeyModifiers inputModifiers) |
|||
{ |
|||
IInputElement? branch = null; |
|||
|
|||
IInputElement? el = element; |
|||
|
|||
while (el != null) |
|||
{ |
|||
if (el.IsPointerOver) |
|||
{ |
|||
branch = el; |
|||
break; |
|||
} |
|||
el = (IInputElement?)el.VisualParent; |
|||
} |
|||
|
|||
el = root.PointerOverElement; |
|||
|
|||
var e = new PointerEventArgs(InputElement.PointerLeaveEvent, el, pointer, root, position, |
|||
timestamp, properties, inputModifiers); |
|||
if (el != null && branch != null && !el.IsAttachedToVisualTree) |
|||
{ |
|||
ClearChildrenPointerOver(e, branch, false); |
|||
} |
|||
|
|||
while (el != null && el != branch) |
|||
{ |
|||
e.Source = el; |
|||
e.Handled = false; |
|||
el.RaiseEvent(e); |
|||
el = (IInputElement?)el.VisualParent; |
|||
} |
|||
|
|||
el = root.PointerOverElement = element; |
|||
_lastPointer = (pointer, root.PointToScreen(position)); |
|||
|
|||
e.RoutedEvent = InputElement.PointerEnterEvent; |
|||
|
|||
while (el != null && el != branch) |
|||
{ |
|||
e.Source = el; |
|||
e.Handled = false; |
|||
el.RaiseEvent(e); |
|||
el = (IInputElement?)el.VisualParent; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
using Avalonia.Input.Raw; |
|||
|
|||
namespace Avalonia.Input |
|||
{ |
|||
internal static class RawInputHelpers |
|||
{ |
|||
public static KeyModifiers ToKeyModifiers(this RawInputModifiers modifiers) => |
|||
(KeyModifiers)(modifiers & RawInputModifiers.KeyboardMask); |
|||
|
|||
public static PointerUpdateKind ToUpdateKind(this RawPointerEventType type) => type switch |
|||
{ |
|||
RawPointerEventType.LeftButtonDown => PointerUpdateKind.LeftButtonPressed, |
|||
RawPointerEventType.LeftButtonUp => PointerUpdateKind.LeftButtonReleased, |
|||
RawPointerEventType.RightButtonDown => PointerUpdateKind.RightButtonPressed, |
|||
RawPointerEventType.RightButtonUp => PointerUpdateKind.RightButtonReleased, |
|||
RawPointerEventType.MiddleButtonDown => PointerUpdateKind.MiddleButtonPressed, |
|||
RawPointerEventType.MiddleButtonUp => PointerUpdateKind.MiddleButtonReleased, |
|||
RawPointerEventType.XButton1Down => PointerUpdateKind.XButton1Pressed, |
|||
RawPointerEventType.XButton1Up => PointerUpdateKind.XButton1Released, |
|||
RawPointerEventType.XButton2Down => PointerUpdateKind.XButton2Pressed, |
|||
RawPointerEventType.XButton2Up => PointerUpdateKind.XButton2Released, |
|||
RawPointerEventType.TouchBegin => PointerUpdateKind.LeftButtonPressed, |
|||
RawPointerEventType.TouchEnd => PointerUpdateKind.LeftButtonReleased, |
|||
_ => PointerUpdateKind.Other |
|||
}; |
|||
} |
|||
} |
|||
@ -1,74 +1,121 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Threading |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the priorities with which jobs can be invoked on a <see cref="Dispatcher"/>.
|
|||
/// </summary>
|
|||
// TODO: These are copied from WPF - many won't apply to Avalonia.
|
|||
public enum DispatcherPriority |
|||
public readonly struct DispatcherPriority : IEquatable<DispatcherPriority>, IComparable<DispatcherPriority> |
|||
{ |
|||
/// <summary>
|
|||
/// The integer value of the priority
|
|||
/// </summary>
|
|||
public int Value { get; } |
|||
|
|||
private DispatcherPriority(int value) |
|||
{ |
|||
Value = value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Minimum possible priority
|
|||
/// </summary>
|
|||
MinValue = 1, |
|||
|
|||
public static readonly DispatcherPriority MinValue = new(0); |
|||
|
|||
/// <summary>
|
|||
/// The job will be processed when the system is idle.
|
|||
/// </summary>
|
|||
SystemIdle = 1, |
|||
[Obsolete("WPF compatibility")] public static readonly DispatcherPriority SystemIdle = MinValue; |
|||
|
|||
/// <summary>
|
|||
/// The job will be processed when the application is idle.
|
|||
/// </summary>
|
|||
ApplicationIdle = 2, |
|||
[Obsolete("WPF compatibility")] public static readonly DispatcherPriority ApplicationIdle = MinValue; |
|||
|
|||
/// <summary>
|
|||
/// The job will be processed after background operations have completed.
|
|||
/// </summary>
|
|||
ContextIdle = 3, |
|||
[Obsolete("WPF compatibility")] public static readonly DispatcherPriority ContextIdle = MinValue; |
|||
|
|||
/// <summary>
|
|||
/// The job will be processed after other non-idle operations have completed.
|
|||
/// The job will be processed with normal priority.
|
|||
/// </summary>
|
|||
Background = 4, |
|||
public static readonly DispatcherPriority Normal = MinValue; |
|||
|
|||
/// <summary>
|
|||
/// The job will be processed with the same priority as input.
|
|||
/// The job will be processed after other non-idle operations have completed.
|
|||
/// </summary>
|
|||
Input = 5, |
|||
public static readonly DispatcherPriority Background = new(1); |
|||
|
|||
/// <summary>
|
|||
/// The job will be processed after layout and render but before input.
|
|||
/// The job will be processed with the same priority as input.
|
|||
/// </summary>
|
|||
Loaded = 6, |
|||
public static readonly DispatcherPriority Input = new(2); |
|||
|
|||
/// <summary>
|
|||
/// The job will be processed with the same priority as render.
|
|||
/// The job will be processed after layout and render but before input.
|
|||
/// </summary>
|
|||
Render = 7, |
|||
public static readonly DispatcherPriority Loaded = new(3); |
|||
|
|||
/// <summary>
|
|||
/// The job will be processed with the same priority as render.
|
|||
/// </summary>
|
|||
Layout = 8, |
|||
|
|||
public static readonly DispatcherPriority Render = new(5); |
|||
|
|||
/// <summary>
|
|||
/// The job will be processed with the same priority as data binding.
|
|||
/// The job will be processed with the same priority as render.
|
|||
/// </summary>
|
|||
DataBind = 9, |
|||
public static readonly DispatcherPriority Layout = new(6); |
|||
|
|||
/// <summary>
|
|||
/// The job will be processed with normal priority.
|
|||
/// The job will be processed with the same priority as data binding.
|
|||
/// </summary>
|
|||
Normal = 10, |
|||
[Obsolete("WPF compatibility")] public static readonly DispatcherPriority DataBind = MinValue; |
|||
|
|||
/// <summary>
|
|||
/// The job will be processed before other asynchronous operations.
|
|||
/// </summary>
|
|||
Send = 11, |
|||
|
|||
public static readonly DispatcherPriority Send = new(7); |
|||
|
|||
/// <summary>
|
|||
/// Maximum possible priority
|
|||
/// </summary>
|
|||
MaxValue = 11 |
|||
public static readonly DispatcherPriority MaxValue = Send; |
|||
|
|||
// Note: unlike ctor this one is validating
|
|||
public static DispatcherPriority FromValue(int value) |
|||
{ |
|||
if (value < MinValue.Value || value > MaxValue.Value) |
|||
throw new ArgumentOutOfRangeException(nameof(value)); |
|||
return new DispatcherPriority(value); |
|||
} |
|||
|
|||
public static implicit operator int(DispatcherPriority priority) => priority.Value; |
|||
|
|||
public static implicit operator DispatcherPriority(int value) => FromValue(value); |
|||
|
|||
/// <inheritdoc />
|
|||
public bool Equals(DispatcherPriority other) => Value == other.Value; |
|||
|
|||
/// <inheritdoc />
|
|||
public override bool Equals(object? obj) => obj is DispatcherPriority other && Equals(other); |
|||
|
|||
/// <inheritdoc />
|
|||
public override int GetHashCode() => Value.GetHashCode(); |
|||
|
|||
public static bool operator ==(DispatcherPriority left, DispatcherPriority right) => left.Value == right.Value; |
|||
|
|||
public static bool operator !=(DispatcherPriority left, DispatcherPriority right) => left.Value != right.Value; |
|||
|
|||
public static bool operator <(DispatcherPriority left, DispatcherPriority right) => left.Value < right.Value; |
|||
|
|||
public static bool operator >(DispatcherPriority left, DispatcherPriority right) => left.Value > right.Value; |
|||
|
|||
public static bool operator <=(DispatcherPriority left, DispatcherPriority right) => left.Value <= right.Value; |
|||
|
|||
public static bool operator >=(DispatcherPriority left, DispatcherPriority right) => left.Value >= right.Value; |
|||
|
|||
/// <inheritdoc />
|
|||
public int CompareTo(DispatcherPriority other) => Value.CompareTo(other.Value); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Utilities |
|||
{ |
|||
internal class EnumHelper |
|||
{ |
|||
#if NET6_0_OR_GREATER
|
|||
public static T Parse<T>(ReadOnlySpan<char> key, bool ignoreCase) where T : struct |
|||
{ |
|||
return Enum.Parse<T>(key, ignoreCase); |
|||
} |
|||
#else
|
|||
public static T Parse<T>(string key, bool ignoreCase) where T : struct |
|||
{ |
|||
return (T)Enum.Parse(typeof(T), key, ignoreCase); |
|||
} |
|||
#endif
|
|||
} |
|||
} |
|||
@ -0,0 +1,236 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Avalonia.Collections.Pooled; |
|||
|
|||
namespace Avalonia.Utilities; |
|||
|
|||
internal class WeakHashList<T> where T : class |
|||
{ |
|||
private struct Key |
|||
{ |
|||
public WeakReference<T>? Weak; |
|||
public T? Strong; |
|||
public int HashCode; |
|||
|
|||
public static Key MakeStrong(T r) => new() |
|||
{ |
|||
HashCode = r.GetHashCode(), |
|||
Strong = r |
|||
}; |
|||
|
|||
public static Key MakeWeak(T r) => new() |
|||
{ |
|||
HashCode = r.GetHashCode(), |
|||
Weak = new WeakReference<T>(r) |
|||
}; |
|||
|
|||
public override int GetHashCode() => HashCode; |
|||
} |
|||
|
|||
class KeyComparer : IEqualityComparer<Key> |
|||
{ |
|||
public bool Equals(Key x, Key y) |
|||
{ |
|||
if (x.HashCode != y.HashCode) |
|||
return false; |
|||
if (x.Strong != null) |
|||
{ |
|||
if (y.Strong != null) |
|||
return x.Strong == y.Strong; |
|||
if (y.Weak == null) |
|||
return false; |
|||
return y.Weak.TryGetTarget(out var weakTarget) && weakTarget == x.Strong; |
|||
} |
|||
else if (y.Strong != null) |
|||
{ |
|||
if (x.Weak == null) |
|||
return false; |
|||
return x.Weak.TryGetTarget(out var weakTarget) && weakTarget == y.Strong; |
|||
} |
|||
else |
|||
{ |
|||
if (x.Weak == null || x.Weak.TryGetTarget(out var xTarget) == false) |
|||
return y.Weak?.TryGetTarget(out _) != true; |
|||
return y.Weak?.TryGetTarget(out var yTarget) == true && xTarget == yTarget; |
|||
} |
|||
} |
|||
|
|||
public int GetHashCode(Key obj) => obj.HashCode; |
|||
public static KeyComparer Instance = new(); |
|||
} |
|||
|
|||
Dictionary<Key, int>? _dic; |
|||
WeakReference<T>?[]? _arr; |
|||
int _arrCount; |
|||
|
|||
public bool IsEmpty => _dic == null || _dic.Count == 0; |
|||
public bool NeedCompact { get; private set; } |
|||
|
|||
public void Add(T item) |
|||
{ |
|||
if (_dic != null) |
|||
{ |
|||
var strongKey = Key.MakeStrong(item); |
|||
if (_dic.TryGetValue(strongKey, out var cnt)) |
|||
_dic[strongKey] = cnt + 1; |
|||
else |
|||
_dic[Key.MakeWeak(item)] = 1; |
|||
return; |
|||
} |
|||
|
|||
if (_arr == null) |
|||
_arr = new WeakReference<T>[8]; |
|||
|
|||
if (_arrCount < _arr.Length) |
|||
{ |
|||
_arr[_arrCount] = new WeakReference<T>(item); |
|||
_arrCount++; |
|||
return; |
|||
} |
|||
|
|||
// Check if something is dead
|
|||
for (var c = 0; c < _arrCount; c++) |
|||
{ |
|||
if (_arr[c]!.TryGetTarget(out _) == false) |
|||
{ |
|||
_arr[c] = new WeakReference<T>(item); |
|||
return; |
|||
} |
|||
} |
|||
|
|||
_dic = new Dictionary<Key, int>(KeyComparer.Instance); |
|||
foreach (var existing in _arr) |
|||
{ |
|||
if (existing!.TryGetTarget(out var target)) |
|||
Add(target); |
|||
} |
|||
_arr = null; |
|||
|
|||
} |
|||
|
|||
public void Remove(T item) |
|||
{ |
|||
if (_arr != null) |
|||
{ |
|||
for (var c = 0; c < _arr.Length; c++) |
|||
{ |
|||
if (_arr[c]?.TryGetTarget(out var target) == true && target == item) |
|||
{ |
|||
_arr[c] = null; |
|||
Compact(); |
|||
return; |
|||
} |
|||
} |
|||
} |
|||
else if (_dic != null) |
|||
{ |
|||
var strongKey = Key.MakeStrong(item); |
|||
|
|||
if (_dic.TryGetValue(strongKey, out var cnt)) |
|||
{ |
|||
if (cnt > 1) |
|||
{ |
|||
_dic[strongKey] = cnt - 1; |
|||
return; |
|||
} |
|||
} |
|||
|
|||
_dic.Remove(strongKey); |
|||
} |
|||
} |
|||
|
|||
private void ArrCompact() |
|||
{ |
|||
if (_arr != null) |
|||
{ |
|||
int empty = -1; |
|||
for (var c = 0; c < _arrCount; c++) |
|||
{ |
|||
var r = _arr[c]; |
|||
//Mark current index as first empty
|
|||
if (r == null && empty == -1) |
|||
empty = c; |
|||
//If current element isn't null and we have an empty one
|
|||
if (r != null && empty != -1) |
|||
{ |
|||
_arr[c] = null; |
|||
_arr[empty] = r; |
|||
empty++; |
|||
} |
|||
} |
|||
|
|||
if (empty != -1) |
|||
_arrCount = empty; |
|||
} |
|||
} |
|||
|
|||
public void Compact() |
|||
{ |
|||
if (_dic != null) |
|||
{ |
|||
PooledList<Key>? toRemove = null; |
|||
foreach (var kvp in _dic) |
|||
{ |
|||
if (kvp.Key.Weak?.TryGetTarget(out _) != true) |
|||
(toRemove ??= new PooledList<Key>()).Add(kvp.Key); |
|||
} |
|||
|
|||
if (toRemove != null) |
|||
{ |
|||
foreach (var k in toRemove) |
|||
_dic.Remove(k); |
|||
toRemove.Dispose(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private static readonly Stack<PooledList<T>> s_listPool = new(); |
|||
|
|||
public static void ReturnToSharedPool(PooledList<T> list) |
|||
{ |
|||
list.Clear(); |
|||
s_listPool.Push(list); |
|||
} |
|||
|
|||
public PooledList<T>? GetAlive(Func<PooledList<T>>? factory = null) |
|||
{ |
|||
PooledList<T>? pooled = null; |
|||
if (_arr != null) |
|||
{ |
|||
bool needCompact = false; |
|||
for (var c = 0; c < _arrCount; c++) |
|||
{ |
|||
if (_arr[c]?.TryGetTarget(out var target) == true) |
|||
(pooled ??= factory?.Invoke() |
|||
?? (s_listPool.Count > 0 |
|||
? s_listPool.Pop() |
|||
: new PooledList<T>())).Add(target!); |
|||
else |
|||
{ |
|||
_arr[c] = null; |
|||
needCompact = true; |
|||
} |
|||
} |
|||
if(needCompact) |
|||
ArrCompact(); |
|||
return pooled; |
|||
} |
|||
if (_dic != null) |
|||
{ |
|||
|
|||
foreach (var kvp in _dic) |
|||
{ |
|||
if (kvp.Key.Weak?.TryGetTarget(out var target) == true) |
|||
(pooled ??= factory?.Invoke() |
|||
?? (s_listPool.Count > 0 |
|||
? s_listPool.Pop() |
|||
: new PooledList<T>())) |
|||
.Add(target!); |
|||
else |
|||
NeedCompact = true; |
|||
} |
|||
} |
|||
|
|||
return pooled; |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
{ |
|||
"$schema": "http://json.schemastore.org/launchsettings.json", |
|||
"profiles": { |
|||
"Compile Sandbox": { |
|||
"commandName": "Project", |
|||
"executablePath": "$(SolutionDir)\\src\\Avalonia.Build.Tasks\\bin\\Debug\\net6.0\\Avalonia.Build.Tasks.exe", |
|||
"commandLineArgs": "$(SolutionDir)\\samples\\Sandbox\\obj\\Debug\\net6.0\\Avalonia\\original.dll $(SolutionDir)\\samples\\Sandbox\\bin\\Debug\\net6.0\\Sandbox.dll.refs $(SolutionDir)\\out.dll" |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<Nullable>enable</Nullable> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3"> |
|||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> |
|||
<PrivateAssets>all</PrivateAssets> |
|||
</PackageReference> |
|||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.1.0" /> |
|||
<Compile Include="..\Shared\SourceGeneratorAttributes.cs" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,14 @@ |
|||
using System.Diagnostics; |
|||
using System.Diagnostics.CodeAnalysis; |
|||
|
|||
namespace System.Runtime.CompilerServices |
|||
{ |
|||
/// <summary>
|
|||
/// Reserved to be used by the compiler for tracking metadata.
|
|||
/// This class should not be used by developers in source code.
|
|||
/// </summary>
|
|||
[ExcludeFromCodeCoverage, DebuggerNonUserCode] |
|||
internal static class IsExternalInit |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,155 @@ |
|||
using System; |
|||
using System.Collections.Immutable; |
|||
using System.Linq; |
|||
using System.Threading; |
|||
using Microsoft.CodeAnalysis; |
|||
using Microsoft.CodeAnalysis.CSharp; |
|||
using Microsoft.CodeAnalysis.CSharp.Syntax; |
|||
|
|||
namespace Avalonia.SourceGenerator |
|||
{ |
|||
[Generator(LanguageNames.CSharp)] |
|||
public class SubtypesFactoryGenerator : IIncrementalGenerator |
|||
{ |
|||
private record struct MethodTarget(IMethodSymbol Method, string MethodDecl, ITypeSymbol BaseType, string Namespace); |
|||
private static readonly string s_attributeName = typeof(SubtypesFactoryAttribute).FullName; |
|||
|
|||
private static bool IsSubtypeOf(ITypeSymbol type, ITypeSymbol baseType) |
|||
{ |
|||
return type.BaseType is not null && (SymbolEqualityComparer.Default.Equals(type.BaseType, baseType) || IsSubtypeOf(type.BaseType, baseType)); |
|||
} |
|||
|
|||
private static void GenerateSubTypes(SourceProductionContext context, MethodTarget methodTarget, ImmutableArray<ITypeSymbol> types) |
|||
{ |
|||
var (method, methodDecl, baseType, @namespace) = methodTarget; |
|||
var candidateTypes = types.Where(i => IsSubtypeOf(i, baseType)).Where(i => $"{i.ContainingNamespace}.".StartsWith($"{@namespace}.")).ToArray(); |
|||
var type = method.ContainingType; |
|||
var isGeneric = type.TypeParameters.Length > 0; |
|||
var isClass = type.TypeKind == TypeKind.Class; |
|||
|
|||
var typeDecl = $"partial {(isClass ? "class" : "struct")} {type.Name}{(isGeneric ? $"<{string.Join(", ", type.TypeParameters)}>" : "")}"; |
|||
var source = $@"using System;
|
|||
using System.Collections.Generic; |
|||
|
|||
namespace {method.ContainingNamespace} |
|||
{{ |
|||
{typeDecl} |
|||
{{ |
|||
{methodDecl} |
|||
{{ |
|||
var hasMatch = false; |
|||
(hasMatch, {method.Parameters[1].Name}) = {method.Parameters[0].Name} switch |
|||
{{ |
|||
{string.Join("\n", candidateTypes.Select(i => $" \"{i.Name}\" => (true, ({method.Parameters[1].Type})new {i}()),"))} |
|||
_ => (false, default({method.Parameters[1].Type})) |
|||
}}; |
|||
|
|||
return hasMatch; |
|||
}} |
|||
}} |
|||
}}";
|
|||
|
|||
context.AddSource($"{type}.{method.MetadataName}.gen.cs", source); |
|||
} |
|||
|
|||
private static MethodTarget? PopulateMethodTargets(GeneratorSyntaxContext context, CancellationToken token) |
|||
{ |
|||
token.ThrowIfCancellationRequested(); |
|||
if (context.Node is MethodDeclarationSyntax method) |
|||
{ |
|||
var attributes = method.AttributeLists.SelectMany(i => i.Attributes); |
|||
var semanticModel = context.SemanticModel; |
|||
foreach (var attribute in attributes) |
|||
{ |
|||
var attributeTypeInfo = semanticModel.GetTypeInfo(attribute); |
|||
if (attributeTypeInfo.Type is null || |
|||
attributeTypeInfo.Type.ToString() != s_attributeName || |
|||
attribute.ArgumentList is null) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
var arguments = attribute.ArgumentList.Arguments; |
|||
if (arguments.Count != 2) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
if (arguments[0].Expression is not TypeOfExpressionSyntax typeOfExpr || |
|||
arguments[1].Expression is not LiteralExpressionSyntax and not IdentifierNameSyntax) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
var type = semanticModel.GetTypeInfo(typeOfExpr.Type); |
|||
var ns = semanticModel.GetConstantValue(arguments[1].Expression); |
|||
var methodDeclInfo = semanticModel.GetDeclaredSymbol(method); |
|||
|
|||
if (type.Type is not ITypeSymbol baseType || |
|||
ns.HasValue is false || |
|||
ns.Value is not string nsValue || |
|||
methodDeclInfo is not IMethodSymbol methodSymbol || |
|||
methodSymbol.Parameters.Length != 2 || |
|||
methodSymbol.Parameters[1].RefKind != RefKind.Out) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
var parameters = new SeparatedSyntaxList<ParameterSyntax>().AddRange(method.ParameterList.Parameters.Select(i => i.WithAttributeLists(new SyntaxList<AttributeListSyntax>()))); |
|||
var methodDecl = method |
|||
.WithAttributeLists(new SyntaxList<AttributeListSyntax>()) |
|||
.WithParameterList(method.ParameterList.WithParameters(parameters)) |
|||
.WithBody(null) |
|||
.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.None)) |
|||
.WithoutTrivia().ToString(); |
|||
|
|||
return new MethodTarget(methodSymbol, methodDecl, baseType, nsValue); |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
public void Initialize(IncrementalGeneratorInitializationContext context) |
|||
{ |
|||
var typesProvider = context.SyntaxProvider.CreateSyntaxProvider( |
|||
static (syntaxNode, token) => |
|||
{ |
|||
token.ThrowIfCancellationRequested(); |
|||
return syntaxNode is ClassDeclarationSyntax or StructDeclarationSyntax; |
|||
}, |
|||
static (syntaxContext, token) => |
|||
{ |
|||
token.ThrowIfCancellationRequested(); |
|||
return syntaxContext.Node is ClassDeclarationSyntax or StructDeclarationSyntax && |
|||
syntaxContext.SemanticModel.GetDeclaredSymbol(syntaxContext.Node) is ITypeSymbol typeSymbol |
|||
? typeSymbol : null; |
|||
}) |
|||
.SelectMany((type, token) => |
|||
{ |
|||
token.ThrowIfCancellationRequested(); |
|||
return type is null ? Array.Empty<ITypeSymbol>() : new ITypeSymbol[] { type }; |
|||
}); |
|||
|
|||
var methodsProvider = context.SyntaxProvider.CreateSyntaxProvider( |
|||
static (syntaxNode, token) => |
|||
{ |
|||
token.ThrowIfCancellationRequested(); |
|||
return syntaxNode is MethodDeclarationSyntax { AttributeLists.Count: > 0 }; |
|||
}, PopulateMethodTargets) |
|||
.SelectMany((method, token) => |
|||
{ |
|||
token.ThrowIfCancellationRequested(); |
|||
return method is null ? Array.Empty<MethodTarget>() : new MethodTarget[] { method.Value }; |
|||
}); |
|||
|
|||
var generateContext = methodsProvider.Combine(typesProvider.Collect()); |
|||
|
|||
context.RegisterSourceOutput(generateContext, static (sourceContext, source) => |
|||
{ |
|||
sourceContext.CancellationToken.ThrowIfCancellationRequested(); |
|||
GenerateSubTypes(sourceContext, source.Left, source.Right); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.SourceGenerator |
|||
{ |
|||
[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = true)] |
|||
internal sealed class SubtypesFactoryAttribute : Attribute |
|||
{ |
|||
public SubtypesFactoryAttribute(Type baseType, string @namespace) |
|||
{ |
|||
BaseType = baseType; |
|||
Namespace = @namespace; |
|||
} |
|||
|
|||
public string Namespace { get; } |
|||
public Type BaseType { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,534 @@ |
|||
#nullable enable |
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
using Avalonia.Controls; |
|||
using Avalonia.Controls.Presenters; |
|||
using Avalonia.Controls.Templates; |
|||
using Avalonia.Input; |
|||
using Avalonia.Input.Raw; |
|||
using Avalonia.Platform; |
|||
using Avalonia.Rendering; |
|||
using Avalonia.UnitTests; |
|||
using Avalonia.VisualTree; |
|||
|
|||
using Moq; |
|||
|
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Base.UnitTests.Input |
|||
{ |
|||
public class PointerOverTests |
|||
{ |
|||
// https://github.com/AvaloniaUI/Avalonia/issues/2821
|
|||
[Fact] |
|||
public void Close_Should_Remove_PointerOver() |
|||
{ |
|||
using var app = UnitTestApplication.Start(new TestServices(inputManager: new InputManager())); |
|||
|
|||
var renderer = new Mock<IRenderer>(); |
|||
var device = CreatePointerDeviceMock().Object; |
|||
var impl = CreateTopLevelImplMock(renderer.Object); |
|||
|
|||
Canvas canvas; |
|||
var root = CreateInputRoot(impl.Object, new Panel |
|||
{ |
|||
Children = |
|||
{ |
|||
(canvas = new Canvas()) |
|||
} |
|||
}); |
|||
|
|||
SetHit(renderer, canvas); |
|||
impl.Object.Input!(CreateRawPointerMovedArgs(device, root)); |
|||
|
|||
Assert.True(canvas.IsPointerOver); |
|||
|
|||
impl.Object.Closed!(); |
|||
|
|||
Assert.False(canvas.IsPointerOver); |
|||
} |
|||
|
|||
[Fact] |
|||
public void MouseMove_Should_Update_IsPointerOver() |
|||
{ |
|||
using var app = UnitTestApplication.Start(new TestServices(inputManager: new InputManager())); |
|||
|
|||
var renderer = new Mock<IRenderer>(); |
|||
var device = CreatePointerDeviceMock().Object; |
|||
var impl = CreateTopLevelImplMock(renderer.Object); |
|||
|
|||
Canvas canvas; |
|||
Border border; |
|||
Decorator decorator; |
|||
|
|||
var root = CreateInputRoot(impl.Object, new Panel |
|||
{ |
|||
Children = |
|||
{ |
|||
(canvas = new Canvas()), |
|||
(border = new Border |
|||
{ |
|||
Child = decorator = new Decorator(), |
|||
}) |
|||
} |
|||
}); |
|||
|
|||
SetHit(renderer, decorator); |
|||
impl.Object.Input!(CreateRawPointerMovedArgs(device, root)); |
|||
|
|||
Assert.True(decorator.IsPointerOver); |
|||
Assert.True(border.IsPointerOver); |
|||
Assert.False(canvas.IsPointerOver); |
|||
Assert.True(root.IsPointerOver); |
|||
|
|||
SetHit(renderer, canvas); |
|||
impl.Object.Input!(CreateRawPointerMovedArgs(device, root)); |
|||
|
|||
Assert.False(decorator.IsPointerOver); |
|||
Assert.False(border.IsPointerOver); |
|||
Assert.True(canvas.IsPointerOver); |
|||
Assert.True(root.IsPointerOver); |
|||
} |
|||
|
|||
|
|||
[Fact] |
|||
public void TouchMove_Should_Not_Set_IsPointerOver() |
|||
{ |
|||
using var app = UnitTestApplication.Start(new TestServices(inputManager: new InputManager())); |
|||
|
|||
var renderer = new Mock<IRenderer>(); |
|||
var device = CreatePointerDeviceMock(pointerType: PointerType.Touch).Object; |
|||
var impl = CreateTopLevelImplMock(renderer.Object); |
|||
|
|||
Canvas canvas; |
|||
|
|||
var root = CreateInputRoot(impl.Object, new Panel |
|||
{ |
|||
Children = |
|||
{ |
|||
(canvas = new Canvas()) |
|||
} |
|||
}); |
|||
|
|||
SetHit(renderer, canvas); |
|||
impl.Object.Input!(CreateRawPointerMovedArgs(device, root)); |
|||
|
|||
Assert.False(canvas.IsPointerOver); |
|||
Assert.False(root.IsPointerOver); |
|||
} |
|||
|
|||
[Fact] |
|||
public void HitTest_Should_Be_Ignored_If_Element_Captured() |
|||
{ |
|||
using var app = UnitTestApplication.Start(new TestServices(inputManager: new InputManager())); |
|||
|
|||
var renderer = new Mock<IRenderer>(); |
|||
var pointer = new Mock<IPointer>(); |
|||
var device = CreatePointerDeviceMock(pointer.Object).Object; |
|||
var impl = CreateTopLevelImplMock(renderer.Object); |
|||
|
|||
Canvas canvas; |
|||
Border border; |
|||
Decorator decorator; |
|||
|
|||
var root = CreateInputRoot(impl.Object, new Panel |
|||
{ |
|||
Children = |
|||
{ |
|||
(canvas = new Canvas()), |
|||
(border = new Border |
|||
{ |
|||
Child = decorator = new Decorator(), |
|||
}) |
|||
} |
|||
}); |
|||
|
|||
SetHit(renderer, canvas); |
|||
pointer.SetupGet(p => p.Captured).Returns(decorator); |
|||
impl.Object.Input!(CreateRawPointerMovedArgs(device, root)); |
|||
|
|||
Assert.True(decorator.IsPointerOver); |
|||
Assert.True(border.IsPointerOver); |
|||
Assert.False(canvas.IsPointerOver); |
|||
Assert.True(root.IsPointerOver); |
|||
} |
|||
|
|||
[Fact] |
|||
public void IsPointerOver_Should_Be_Updated_When_Child_Sets_Handled_True() |
|||
{ |
|||
using var app = UnitTestApplication.Start(new TestServices(inputManager: new InputManager())); |
|||
|
|||
var renderer = new Mock<IRenderer>(); |
|||
var device = CreatePointerDeviceMock().Object; |
|||
var impl = CreateTopLevelImplMock(renderer.Object); |
|||
|
|||
Canvas canvas; |
|||
Border border; |
|||
Decorator decorator; |
|||
|
|||
var root = CreateInputRoot(impl.Object, new Panel |
|||
{ |
|||
Children = |
|||
{ |
|||
(canvas = new Canvas()), |
|||
(border = new Border |
|||
{ |
|||
Child = decorator = new Decorator(), |
|||
}) |
|||
} |
|||
}); |
|||
|
|||
SetHit(renderer, canvas); |
|||
impl.Object.Input!(CreateRawPointerMovedArgs(device, root)); |
|||
|
|||
Assert.False(decorator.IsPointerOver); |
|||
Assert.False(border.IsPointerOver); |
|||
Assert.True(canvas.IsPointerOver); |
|||
Assert.True(root.IsPointerOver); |
|||
|
|||
// Ensure that e.Handled is reset between controls.
|
|||
root.PointerMoved += (s, e) => e.Handled = true; |
|||
decorator.PointerEnter += (s, e) => e.Handled = true; |
|||
|
|||
SetHit(renderer, decorator); |
|||
impl.Object.Input!(CreateRawPointerMovedArgs(device, root)); |
|||
|
|||
Assert.True(decorator.IsPointerOver); |
|||
Assert.True(border.IsPointerOver); |
|||
Assert.False(canvas.IsPointerOver); |
|||
Assert.True(root.IsPointerOver); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Pointer_Enter_Move_Leave_Should_Be_Followed() |
|||
{ |
|||
using var app = UnitTestApplication.Start(new TestServices(inputManager: new InputManager())); |
|||
|
|||
var renderer = new Mock<IRenderer>(); |
|||
var deviceMock = CreatePointerDeviceMock(); |
|||
var impl = CreateTopLevelImplMock(renderer.Object); |
|||
var result = new List<(object?, string)>(); |
|||
|
|||
void HandleEvent(object? sender, PointerEventArgs e) |
|||
{ |
|||
result.Add((sender, e.RoutedEvent!.Name)); |
|||
} |
|||
|
|||
Canvas canvas; |
|||
Border border; |
|||
Decorator decorator; |
|||
|
|||
var root = CreateInputRoot(impl.Object, new Panel |
|||
{ |
|||
Children = |
|||
{ |
|||
(canvas = new Canvas()), |
|||
(border = new Border |
|||
{ |
|||
Child = decorator = new Decorator(), |
|||
}) |
|||
} |
|||
}); |
|||
|
|||
AddEnterLeaveHandlers(HandleEvent, canvas, decorator); |
|||
|
|||
// Enter decorator
|
|||
SetHit(renderer, decorator); |
|||
SetMove(deviceMock, root, decorator); |
|||
impl.Object.Input!(CreateRawPointerMovedArgs(deviceMock.Object, root)); |
|||
|
|||
// Leave decorator
|
|||
SetHit(renderer, canvas); |
|||
SetMove(deviceMock, root, canvas); |
|||
impl.Object.Input!(CreateRawPointerMovedArgs(deviceMock.Object, root)); |
|||
|
|||
Assert.Equal( |
|||
new[] |
|||
{ |
|||
((object?)decorator, "PointerEnter"), |
|||
(decorator, "PointerMove"), |
|||
(decorator, "PointerLeave"), |
|||
(canvas, "PointerEnter"), |
|||
(canvas, "PointerMove") |
|||
}, |
|||
result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void PointerEnter_Leave_Should_Be_Raised_In_Correct_Order() |
|||
{ |
|||
using var app = UnitTestApplication.Start(new TestServices(inputManager: new InputManager())); |
|||
|
|||
var renderer = new Mock<IRenderer>(); |
|||
var deviceMock = CreatePointerDeviceMock(); |
|||
var impl = CreateTopLevelImplMock(renderer.Object); |
|||
var result = new List<(object?, string)>(); |
|||
|
|||
void HandleEvent(object? sender, PointerEventArgs e) |
|||
{ |
|||
result.Add((sender, e.RoutedEvent!.Name)); |
|||
} |
|||
|
|||
Canvas canvas; |
|||
Border border; |
|||
Decorator decorator; |
|||
|
|||
var root = CreateInputRoot(impl.Object, new Panel |
|||
{ |
|||
Children = |
|||
{ |
|||
(canvas = new Canvas()), |
|||
(border = new Border |
|||
{ |
|||
Child = decorator = new Decorator(), |
|||
}) |
|||
} |
|||
}); |
|||
|
|||
SetHit(renderer, canvas); |
|||
impl.Object.Input!(CreateRawPointerMovedArgs(deviceMock.Object, root)); |
|||
|
|||
AddEnterLeaveHandlers(HandleEvent, root, canvas, border, decorator); |
|||
|
|||
SetHit(renderer, decorator); |
|||
impl.Object.Input!(CreateRawPointerMovedArgs(deviceMock.Object, root)); |
|||
|
|||
Assert.Equal( |
|||
new[] |
|||
{ |
|||
((object?)canvas, "PointerLeave"), |
|||
(decorator, "PointerEnter"), |
|||
(border, "PointerEnter"), |
|||
}, |
|||
result); |
|||
} |
|||
|
|||
// https://github.com/AvaloniaUI/Avalonia/issues/7896
|
|||
[Fact] |
|||
public void PointerEnter_Leave_Should_Set_Correct_Position() |
|||
{ |
|||
using var app = UnitTestApplication.Start(new TestServices(inputManager: new InputManager())); |
|||
|
|||
var expectedPosition = new Point(15, 15); |
|||
var renderer = new Mock<IRenderer>(); |
|||
var deviceMock = CreatePointerDeviceMock(); |
|||
var impl = CreateTopLevelImplMock(renderer.Object); |
|||
var result = new List<(object?, string, Point)>(); |
|||
|
|||
void HandleEvent(object? sender, PointerEventArgs e) |
|||
{ |
|||
result.Add((sender, e.RoutedEvent!.Name, e.GetPosition(null))); |
|||
} |
|||
|
|||
Canvas canvas; |
|||
|
|||
var root = CreateInputRoot(impl.Object, new Panel |
|||
{ |
|||
Children = |
|||
{ |
|||
(canvas = new Canvas()) |
|||
} |
|||
}); |
|||
|
|||
AddEnterLeaveHandlers(HandleEvent, root, canvas); |
|||
|
|||
SetHit(renderer, canvas); |
|||
impl.Object.Input!(CreateRawPointerMovedArgs(deviceMock.Object, root, expectedPosition)); |
|||
|
|||
SetHit(renderer, null); |
|||
impl.Object.Input!(CreateRawPointerMovedArgs(deviceMock.Object, root, expectedPosition)); |
|||
|
|||
Assert.Equal( |
|||
new[] |
|||
{ |
|||
((object?)canvas, "PointerEnter", expectedPosition), |
|||
(root, "PointerEnter", expectedPosition), |
|||
(canvas, "PointerLeave", expectedPosition), |
|||
(root, "PointerLeave", expectedPosition) |
|||
}, |
|||
result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Render_Invalidation_Should_Affect_PointerOver() |
|||
{ |
|||
using var app = UnitTestApplication.Start(new TestServices(inputManager: new InputManager())); |
|||
|
|||
var renderer = new Mock<IRenderer>(); |
|||
var deviceMock = CreatePointerDeviceMock(); |
|||
var impl = CreateTopLevelImplMock(renderer.Object); |
|||
|
|||
var invalidateRect = new Rect(0, 0, 15, 15); |
|||
|
|||
Canvas canvas; |
|||
|
|||
var root = CreateInputRoot(impl.Object, new Panel |
|||
{ |
|||
Children = |
|||
{ |
|||
(canvas = new Canvas()) |
|||
} |
|||
}); |
|||
|
|||
// Let input know about latest device.
|
|||
SetHit(renderer, canvas); |
|||
impl.Object.Input!(CreateRawPointerMovedArgs(deviceMock.Object, root)); |
|||
Assert.True(canvas.IsPointerOver); |
|||
|
|||
SetHit(renderer, canvas); |
|||
renderer.Raise(r => r.SceneInvalidated += null, new SceneInvalidatedEventArgs((IRenderRoot)root, invalidateRect)); |
|||
Assert.True(canvas.IsPointerOver); |
|||
|
|||
// Raise SceneInvalidated again, but now hide element from the hittest.
|
|||
SetHit(renderer, null); |
|||
renderer.Raise(r => r.SceneInvalidated += null, new SceneInvalidatedEventArgs((IRenderRoot)root, invalidateRect)); |
|||
Assert.False(canvas.IsPointerOver); |
|||
} |
|||
|
|||
// https://github.com/AvaloniaUI/Avalonia/issues/7748
|
|||
[Fact] |
|||
public void LeaveWindow_Should_Reset_PointerOver() |
|||
{ |
|||
using var app = UnitTestApplication.Start(new TestServices(inputManager: new InputManager())); |
|||
|
|||
var renderer = new Mock<IRenderer>(); |
|||
var deviceMock = CreatePointerDeviceMock(); |
|||
var impl = CreateTopLevelImplMock(renderer.Object); |
|||
|
|||
var lastClientPosition = new Point(1, 5); |
|||
var invalidateRect = new Rect(0, 0, 15, 15); |
|||
var result = new List<(object?, string, Point)>(); |
|||
|
|||
void HandleEvent(object? sender, PointerEventArgs e) |
|||
{ |
|||
result.Add((sender, e.RoutedEvent!.Name, e.GetPosition(null))); |
|||
} |
|||
|
|||
Canvas canvas; |
|||
|
|||
var root = CreateInputRoot(impl.Object, new Panel |
|||
{ |
|||
Children = |
|||
{ |
|||
(canvas = new Canvas()) |
|||
} |
|||
}); |
|||
|
|||
AddEnterLeaveHandlers(HandleEvent, root, canvas); |
|||
|
|||
// Init pointer over.
|
|||
SetHit(renderer, canvas); |
|||
impl.Object.Input!(CreateRawPointerMovedArgs(deviceMock.Object, root, lastClientPosition)); |
|||
Assert.True(canvas.IsPointerOver); |
|||
|
|||
// Send LeaveWindow.
|
|||
impl.Object.Input!(new RawPointerEventArgs(deviceMock.Object, 0, root, RawPointerEventType.LeaveWindow, new Point(), default)); |
|||
Assert.False(canvas.IsPointerOver); |
|||
|
|||
Assert.Equal( |
|||
new[] |
|||
{ |
|||
((object?)canvas, "PointerEnter", lastClientPosition), |
|||
(root, "PointerEnter", lastClientPosition), |
|||
(canvas, "PointerLeave", lastClientPosition), |
|||
(root, "PointerLeave", lastClientPosition), |
|||
}, |
|||
result); |
|||
} |
|||
|
|||
private static void AddEnterLeaveHandlers( |
|||
EventHandler<PointerEventArgs> handler, |
|||
params IInputElement[] controls) |
|||
{ |
|||
foreach (var c in controls) |
|||
{ |
|||
c.PointerEnter += handler; |
|||
c.PointerLeave += handler; |
|||
c.PointerMoved += handler; |
|||
} |
|||
} |
|||
|
|||
private static void SetHit(Mock<IRenderer> renderer, IControl? hit) |
|||
{ |
|||
renderer.Setup(x => x.HitTest(It.IsAny<Point>(), It.IsAny<IVisual>(), It.IsAny<Func<IVisual, bool>>())) |
|||
.Returns(hit is null ? Array.Empty<IControl>() : new[] { hit }); |
|||
|
|||
renderer.Setup(x => x.HitTestFirst(It.IsAny<Point>(), It.IsAny<IVisual>(), It.IsAny<Func<IVisual, bool>>())) |
|||
.Returns(hit); |
|||
} |
|||
|
|||
private static void SetMove(Mock<IPointerDevice> deviceMock, IInputRoot root, IInputElement element) |
|||
{ |
|||
deviceMock.Setup(d => d.ProcessRawEvent(It.IsAny<RawPointerEventArgs>())) |
|||
.Callback(() => element.RaiseEvent(CreatePointerMovedArgs(root, element))); |
|||
} |
|||
|
|||
private static Mock<IWindowImpl> CreateTopLevelImplMock(IRenderer renderer) |
|||
{ |
|||
var impl = new Mock<IWindowImpl>(); |
|||
impl.DefaultValue = DefaultValue.Mock; |
|||
impl.SetupAllProperties(); |
|||
impl.SetupGet(r => r.RenderScaling).Returns(1); |
|||
impl.Setup(r => r.CreateRenderer(It.IsAny<IRenderRoot>())).Returns(renderer); |
|||
impl.Setup(r => r.PointToScreen(It.IsAny<Point>())).Returns<Point>(p => new PixelPoint((int)p.X, (int)p.Y)); |
|||
impl.Setup(r => r.PointToClient(It.IsAny<PixelPoint>())).Returns<PixelPoint>(p => new Point(p.X, p.Y)); |
|||
return impl; |
|||
} |
|||
|
|||
private static IInputRoot CreateInputRoot(IWindowImpl impl, IControl child) |
|||
{ |
|||
var root = new Window(impl) |
|||
{ |
|||
Width = 100, |
|||
Height = 100, |
|||
Content = child, |
|||
Template = new FuncControlTemplate<Window>((w, _) => new ContentPresenter |
|||
{ |
|||
Content = w.Content |
|||
}) |
|||
}; |
|||
root.Show(); |
|||
return root; |
|||
} |
|||
|
|||
private static IInputRoot CreateInputRoot(IRenderer renderer, IControl child) |
|||
{ |
|||
return CreateInputRoot(CreateTopLevelImplMock(renderer).Object, child); |
|||
} |
|||
|
|||
private static RawPointerEventArgs CreateRawPointerMovedArgs( |
|||
IPointerDevice pointerDevice, |
|||
IInputRoot root, |
|||
Point? positition = null) |
|||
{ |
|||
return new RawPointerEventArgs(pointerDevice, 0, root, RawPointerEventType.Move, |
|||
positition ?? default, default); |
|||
} |
|||
|
|||
private static PointerEventArgs CreatePointerMovedArgs( |
|||
IInputRoot root, IInputElement? source, Point? positition = null) |
|||
{ |
|||
return new PointerEventArgs(InputElement.PointerMovedEvent, source, new Mock<IPointer>().Object, root, |
|||
positition ?? default, default, PointerPointProperties.None, KeyModifiers.None); |
|||
} |
|||
|
|||
private static Mock<IPointerDevice> CreatePointerDeviceMock( |
|||
IPointer? pointer = null, |
|||
PointerType pointerType = PointerType.Mouse) |
|||
{ |
|||
if (pointer is null) |
|||
{ |
|||
var pointerMock = new Mock<IPointer>(); |
|||
pointerMock.SetupGet(p => p.Type).Returns(pointerType); |
|||
pointer = pointerMock.Object; |
|||
} |
|||
|
|||
var pointerDevice = new Mock<IPointerDevice>(); |
|||
pointerDevice.Setup(d => d.TryGetPointer(It.IsAny<RawPointerEventArgs>())) |
|||
.Returns(pointer); |
|||
|
|||
return pointerDevice; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,113 @@ |
|||
using System; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Platform; |
|||
using Avalonia.PlatformSupport; |
|||
using Avalonia.Styling; |
|||
using Avalonia.UnitTests; |
|||
using BenchmarkDotNet.Attributes; |
|||
using Moq; |
|||
|
|||
namespace Avalonia.Benchmarks.Styling |
|||
{ |
|||
[MemoryDiagnoser] |
|||
public class ResourceBenchmarks : IDisposable |
|||
{ |
|||
private readonly Control _searchStart; |
|||
private readonly IDisposable _app; |
|||
|
|||
private static IDisposable CreateApp() |
|||
{ |
|||
var services = new TestServices( |
|||
assetLoader: new AssetLoader(), |
|||
globalClock: new MockGlobalClock(), |
|||
platform: new AppBuilder().RuntimePlatform, |
|||
renderInterface: new MockPlatformRenderInterface(), |
|||
standardCursorFactory: Mock.Of<ICursorFactory>(), |
|||
styler: new Styler(), |
|||
theme: () => CreateTheme(), |
|||
threadingInterface: new NullThreadingPlatform(), |
|||
fontManagerImpl: new MockFontManagerImpl(), |
|||
textShaperImpl: new MockTextShaperImpl(), |
|||
windowingPlatform: new MockWindowingPlatform()); |
|||
|
|||
return UnitTestApplication.Start(services); |
|||
} |
|||
|
|||
private static Styles CreateTheme() |
|||
{ |
|||
AssetLoader.RegisterResUriParsers(); |
|||
|
|||
var preHost = new Style(); |
|||
preHost.Resources.Add("preTheme", null); |
|||
|
|||
var postHost = new Style(); |
|||
postHost.Resources.Add("postTheme", null); |
|||
|
|||
return new Styles |
|||
{ |
|||
preHost, |
|||
new TestStyles(50, 3, 5), |
|||
postHost |
|||
}; |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
_app.Dispose(); |
|||
} |
|||
|
|||
public ResourceBenchmarks() |
|||
{ |
|||
_searchStart = new Button(); |
|||
|
|||
_app = CreateApp(); |
|||
|
|||
Decorator root = new TestRoot(true, null) |
|||
{ |
|||
Renderer = new NullRenderer() |
|||
}; |
|||
|
|||
var current = root; |
|||
|
|||
for (int i = 0; i < 10; i++) |
|||
{ |
|||
var child = new Decorator(); |
|||
|
|||
current.Child = child; |
|||
|
|||
current = child; |
|||
} |
|||
|
|||
current.Child = _searchStart; |
|||
} |
|||
|
|||
private const int LookupCount = 100; |
|||
|
|||
[Benchmark] |
|||
public void FindPreResource() |
|||
{ |
|||
for (int i = 0; i < LookupCount; ++i) |
|||
{ |
|||
_searchStart.FindResource("preTheme"); |
|||
} |
|||
} |
|||
|
|||
[Benchmark] |
|||
public void FindPostResource() |
|||
{ |
|||
for (int i = 0; i < LookupCount; ++i) |
|||
{ |
|||
_searchStart.FindResource("postTheme"); |
|||
} |
|||
} |
|||
|
|||
[Benchmark] |
|||
public void FindNotExistingResource() |
|||
{ |
|||
for (int i = 0; i < LookupCount; ++i) |
|||
{ |
|||
_searchStart.FindResource("notPresent"); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue