192 changed files with 3511 additions and 2345 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,3 +1,4 @@ |
|||||
Compat issues with assembly Avalonia.Base: |
Compat issues with assembly Avalonia.Base: |
||||
|
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. |
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: 1 |
Total Issues: 2 |
||||
|
|||||
@ -0,0 +1,43 @@ |
|||||
|
namespace Avalonia |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Provides extensions for <see cref="AvaloniaPropertyChangedEventArgs"/>.
|
||||
|
/// </summary>
|
||||
|
public static class AvaloniaPropertyChangedExtensions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets a typed value from <see cref="AvaloniaPropertyChangedEventArgs.OldValue"/>.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The value type.</typeparam>
|
||||
|
/// <param name="e">The event args.</param>
|
||||
|
/// <returns>The value.</returns>
|
||||
|
public static T GetOldValue<T>(this AvaloniaPropertyChangedEventArgs e) |
||||
|
{ |
||||
|
return ((AvaloniaPropertyChangedEventArgs<T>)e).OldValue.GetValueOrDefault()!; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a typed value from <see cref="AvaloniaPropertyChangedEventArgs.NewValue"/>.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The value type.</typeparam>
|
||||
|
/// <param name="e">The event args.</param>
|
||||
|
/// <returns>The value.</returns>
|
||||
|
public static T GetNewValue<T>(this AvaloniaPropertyChangedEventArgs e) |
||||
|
{ |
||||
|
return ((AvaloniaPropertyChangedEventArgs<T>)e).NewValue.GetValueOrDefault()!; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a typed value from <see cref="AvaloniaPropertyChangedEventArgs.OldValue"/> and
|
||||
|
/// <see cref="AvaloniaPropertyChangedEventArgs.NewValue"/>.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The value type.</typeparam>
|
||||
|
/// <param name="e">The event args.</param>
|
||||
|
/// <returns>The value.</returns>
|
||||
|
public static (T oldValue, T newValue) GetOldAndNewValue<T>(this AvaloniaPropertyChangedEventArgs e) |
||||
|
{ |
||||
|
var ev = (AvaloniaPropertyChangedEventArgs<T>)e; |
||||
|
return (ev.OldValue.GetValueOrDefault()!, ev.NewValue.GetValueOrDefault()!); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,17 +1,31 @@ |
|||||
using System; |
using System; |
||||
using Avalonia.VisualTree; |
using Avalonia.VisualTree; |
||||
|
using Avalonia.Input.Raw; |
||||
|
|
||||
namespace Avalonia.Input |
namespace Avalonia.Input |
||||
{ |
{ |
||||
public interface IPointerDevice : IInputDevice |
public interface IPointerDevice : IInputDevice |
||||
{ |
{ |
||||
|
/// <inheritdoc cref="IPointer.Captured" />
|
||||
[Obsolete("Use IPointer")] |
[Obsolete("Use IPointer")] |
||||
IInputElement? Captured { get; } |
IInputElement? Captured { get; } |
||||
|
|
||||
|
/// <inheritdoc cref="IPointer.Capture(IInputElement?)" />
|
||||
[Obsolete("Use IPointer")] |
[Obsolete("Use IPointer")] |
||||
void Capture(IInputElement? control); |
void Capture(IInputElement? control); |
||||
|
|
||||
|
/// <inheritdoc cref="PointerEventArgs.GetPosition(IVisual?)" />
|
||||
[Obsolete("Use PointerEventArgs.GetPosition")] |
[Obsolete("Use PointerEventArgs.GetPosition")] |
||||
Point GetPosition(IVisual relativeTo); |
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,17 +0,0 @@ |
|||||
using Avalonia.Data; |
|
||||
|
|
||||
namespace Avalonia.PropertyStore |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Represents an entity that can receive change notifications in a <see cref="ValueStore"/>.
|
|
||||
/// </summary>
|
|
||||
internal interface IValueSink |
|
||||
{ |
|
||||
void ValueChanged<T>(AvaloniaPropertyChangedEventArgs<T> change); |
|
||||
|
|
||||
void Completed<T>( |
|
||||
StyledPropertyBase<T> property, |
|
||||
IPriorityValueEntry entry, |
|
||||
Optional<T> oldValue); |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,45 @@ |
|||||
|
using Avalonia.Data; |
||||
|
|
||||
|
namespace Avalonia.PropertyStore |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Represents a union type of <see cref="ValueStore"/> and <see cref="PriorityValue{T}"/>,
|
||||
|
/// which are the valid owners of a value store <see cref="IValue"/>.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The value type.</typeparam>
|
||||
|
internal readonly struct ValueOwner<T> |
||||
|
{ |
||||
|
private readonly ValueStore? _store; |
||||
|
private readonly PriorityValue<T>? _priorityValue; |
||||
|
|
||||
|
public ValueOwner(ValueStore o) |
||||
|
{ |
||||
|
_store = o; |
||||
|
_priorityValue = null; |
||||
|
} |
||||
|
|
||||
|
public ValueOwner(PriorityValue<T> v) |
||||
|
{ |
||||
|
_store = null; |
||||
|
_priorityValue = v; |
||||
|
} |
||||
|
|
||||
|
public bool IsValueStore => _store is not null; |
||||
|
|
||||
|
public void Completed(StyledPropertyBase<T> property, IPriorityValueEntry entry, Optional<T> oldValue) |
||||
|
{ |
||||
|
if (_store is not null) |
||||
|
_store?.Completed(property, entry, oldValue); |
||||
|
else |
||||
|
_priorityValue!.Completed(entry, oldValue); |
||||
|
} |
||||
|
|
||||
|
public void ValueChanged(AvaloniaPropertyChangedEventArgs<T> e) |
||||
|
{ |
||||
|
if (_store is not null) |
||||
|
_store?.ValueChanged(e); |
||||
|
else |
||||
|
_priorityValue!.ValueChanged(e); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,74 +1,121 @@ |
|||||
|
using System; |
||||
|
|
||||
namespace Avalonia.Threading |
namespace Avalonia.Threading |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// Defines the priorities with which jobs can be invoked on a <see cref="Dispatcher"/>.
|
/// Defines the priorities with which jobs can be invoked on a <see cref="Dispatcher"/>.
|
||||
/// </summary>
|
/// </summary>
|
||||
// TODO: These are copied from WPF - many won't apply to Avalonia.
|
public readonly struct DispatcherPriority : IEquatable<DispatcherPriority>, IComparable<DispatcherPriority> |
||||
public enum DispatcherPriority |
|
||||
{ |
{ |
||||
|
/// <summary>
|
||||
|
/// The integer value of the priority
|
||||
|
/// </summary>
|
||||
|
public int Value { get; } |
||||
|
|
||||
|
private DispatcherPriority(int value) |
||||
|
{ |
||||
|
Value = value; |
||||
|
} |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// Minimum possible priority
|
/// Minimum possible priority
|
||||
/// </summary>
|
/// </summary>
|
||||
MinValue = 1, |
public static readonly DispatcherPriority MinValue = new(0); |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// The job will be processed when the system is idle.
|
/// The job will be processed when the system is idle.
|
||||
/// </summary>
|
/// </summary>
|
||||
SystemIdle = 1, |
[Obsolete("WPF compatibility")] public static readonly DispatcherPriority SystemIdle = MinValue; |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// The job will be processed when the application is idle.
|
/// The job will be processed when the application is idle.
|
||||
/// </summary>
|
/// </summary>
|
||||
ApplicationIdle = 2, |
[Obsolete("WPF compatibility")] public static readonly DispatcherPriority ApplicationIdle = MinValue; |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// The job will be processed after background operations have completed.
|
/// The job will be processed after background operations have completed.
|
||||
/// </summary>
|
/// </summary>
|
||||
ContextIdle = 3, |
[Obsolete("WPF compatibility")] public static readonly DispatcherPriority ContextIdle = MinValue; |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// The job will be processed after other non-idle operations have completed.
|
/// The job will be processed with normal priority.
|
||||
/// </summary>
|
/// </summary>
|
||||
Background = 4, |
public static readonly DispatcherPriority Normal = MinValue; |
||||
|
|
||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||
Input = 5, |
public static readonly DispatcherPriority Background = new(1); |
||||
|
|
||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||
Loaded = 6, |
public static readonly DispatcherPriority Input = new(2); |
||||
|
|
||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||
Render = 7, |
public static readonly DispatcherPriority Loaded = new(3); |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// The job will be processed with the same priority as render.
|
/// The job will be processed with the same priority as render.
|
||||
/// </summary>
|
/// </summary>
|
||||
Layout = 8, |
public static readonly DispatcherPriority Render = new(5); |
||||
|
|
||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||
DataBind = 9, |
public static readonly DispatcherPriority Layout = new(6); |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// The job will be processed with normal priority.
|
/// The job will be processed with the same priority as data binding.
|
||||
/// </summary>
|
/// </summary>
|
||||
Normal = 10, |
[Obsolete("WPF compatibility")] public static readonly DispatcherPriority DataBind = MinValue; |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// The job will be processed before other asynchronous operations.
|
/// The job will be processed before other asynchronous operations.
|
||||
/// </summary>
|
/// </summary>
|
||||
Send = 11, |
public static readonly DispatcherPriority Send = new(7); |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// Maximum possible priority
|
/// Maximum possible priority
|
||||
/// </summary>
|
/// </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
|
||||
|
} |
||||
|
} |
||||
@ -1,32 +0,0 @@ |
|||||
namespace Avalonia.Utilities |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// A visitor to resolve an untyped <see cref="AvaloniaProperty"/> to a typed property.
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="TData">The type of user data passed.</typeparam>
|
|
||||
/// <remarks>
|
|
||||
/// Pass an instance that implements this interface to
|
|
||||
/// <see cref="AvaloniaProperty.Accept{TData}(IAvaloniaPropertyVisitor{TData}, ref TData)"/>
|
|
||||
/// in order to resolve un untyped <see cref="AvaloniaProperty"/> to a typed
|
|
||||
/// <see cref="StyledPropertyBase{TValue}"/> or <see cref="DirectPropertyBase{TValue}"/>.
|
|
||||
/// </remarks>
|
|
||||
public interface IAvaloniaPropertyVisitor<TData> |
|
||||
where TData : struct |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Called when the property is a styled property.
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="T">The property value type.</typeparam>
|
|
||||
/// <param name="property">The property.</param>
|
|
||||
/// <param name="data">The user data.</param>
|
|
||||
void Visit<T>(StyledPropertyBase<T> property, ref TData data); |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Called when the property is a direct property.
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="T">The property value type.</typeparam>
|
|
||||
/// <param name="property">The property.</param>
|
|
||||
/// <param name="data">The user data.</param>
|
|
||||
void Visit<T>(DirectPropertyBase<T> property, ref TData data); |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,241 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Avalonia.Collections.Pooled; |
||||
|
|
||||
|
namespace Avalonia.Utilities; |
||||
|
|
||||
|
internal class WeakHashList<T> where T : class |
||||
|
{ |
||||
|
public const int DefaultArraySize = 8; |
||||
|
|
||||
|
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 is not null ? _dic.Count == 0 : _arrCount == 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>[DefaultArraySize]; |
||||
|
|
||||
|
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); |
||||
|
} |
||||
|
|
||||
|
Add(item); |
||||
|
|
||||
|
_arr = null; |
||||
|
_arrCount = 0; |
||||
|
} |
||||
|
|
||||
|
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; |
||||
|
ArrCompact(); |
||||
|
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" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1 +1,4 @@ |
|||||
Total Issues: 0 |
Compat issues with assembly Avalonia.Controls.DataGrid: |
||||
|
MembersMustExist : Member 'protected void Avalonia.Controls.DataGridCheckBoxColumn.OnPropertyChanged<T>(Avalonia.AvaloniaPropertyChangedEventArgs<T>)' does not exist in the implementation but it does exist in the contract. |
||||
|
MembersMustExist : Member 'protected void Avalonia.Controls.DataGridTextColumn.OnPropertyChanged<T>(Avalonia.AvaloniaPropertyChangedEventArgs<T>)' does not exist in the implementation but it does exist in the contract. |
||||
|
Total Issues: 2 |
||||
|
|||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue