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: |
|||
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: 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 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,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 |
|||
{ |
|||
/// <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
|
|||
} |
|||
} |
|||
@ -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