committed by
GitHub
71 changed files with 1013 additions and 390 deletions
@ -1,5 +1,5 @@ |
|||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
||||
<ItemGroup> |
<ItemGroup> |
||||
<PackageReference Include="ReactiveUI" Version="12.1.1" /> |
<PackageReference Include="ReactiveUI" Version="13.2.10" /> |
||||
</ItemGroup> |
</ItemGroup> |
||||
</Project> |
</Project> |
||||
|
|||||
@ -1,5 +1,5 @@ |
|||||
<?xml version="1.0" encoding="utf-8"?> |
<?xml version="1.0" encoding="utf-8"?> |
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="ControlCatalog.Android" android:versionCode="1" android:versionName="1.0" android:installLocation="auto"> |
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="ControlCatalog.Android" android:versionCode="1" android:versionName="1.0" android:installLocation="auto"> |
||||
<uses-sdk android:targetSdkVersion="29" /> |
<uses-sdk android:targetSdkVersion="30" /> |
||||
<application android:label="ControlCatalog.Android"></application> |
<application android:label="ControlCatalog.Android"></application> |
||||
</manifest> |
</manifest> |
||||
@ -1,47 +0,0 @@ |
|||||
using Android.App; |
|
||||
using Android.OS; |
|
||||
|
|
||||
namespace Avalonia.Android |
|
||||
{ |
|
||||
internal class ActivityTracker : Java.Lang.Object, global::Android.App.Application.IActivityLifecycleCallbacks |
|
||||
{ |
|
||||
public static Activity Current { get; private set; } |
|
||||
public void OnActivityCreated(Activity activity, Bundle savedInstanceState) |
|
||||
{ |
|
||||
Current = activity; |
|
||||
} |
|
||||
|
|
||||
public void OnActivityDestroyed(Activity activity) |
|
||||
{ |
|
||||
if (Current == activity) |
|
||||
Current = null; |
|
||||
} |
|
||||
|
|
||||
public void OnActivityPaused(Activity activity) |
|
||||
{ |
|
||||
if (Current == activity) |
|
||||
Current = null; |
|
||||
} |
|
||||
|
|
||||
public void OnActivityResumed(Activity activity) |
|
||||
{ |
|
||||
Current = activity; |
|
||||
} |
|
||||
|
|
||||
public void OnActivitySaveInstanceState(Activity activity, Bundle outState) |
|
||||
{ |
|
||||
Current = activity; |
|
||||
} |
|
||||
|
|
||||
public void OnActivityStarted(Activity activity) |
|
||||
{ |
|
||||
Current = activity; |
|
||||
} |
|
||||
|
|
||||
public void OnActivityStopped(Activity activity) |
|
||||
{ |
|
||||
if (Current == activity) |
|
||||
Current = null; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,101 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Reactive.Disposables; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
using Android.OS; |
||||
|
using Android.Views; |
||||
|
|
||||
|
using Avalonia.Rendering; |
||||
|
|
||||
|
using Java.Lang; |
||||
|
|
||||
|
namespace Avalonia.Android |
||||
|
{ |
||||
|
internal sealed class ChoreographerTimer : Java.Lang.Object, IRenderTimer, Choreographer.IFrameCallback |
||||
|
{ |
||||
|
private readonly object _lock = new object(); |
||||
|
|
||||
|
private readonly Thread _thread; |
||||
|
private readonly TaskCompletionSource<Choreographer> _choreographer = new TaskCompletionSource<Choreographer>(); |
||||
|
|
||||
|
private readonly ISet<AvaloniaView> _views = new HashSet<AvaloniaView>(); |
||||
|
|
||||
|
private Action<TimeSpan> _tick; |
||||
|
private int _count; |
||||
|
|
||||
|
public ChoreographerTimer() |
||||
|
{ |
||||
|
_thread = new Thread(Loop); |
||||
|
_thread.Start(); |
||||
|
} |
||||
|
|
||||
|
public event Action<TimeSpan> Tick |
||||
|
{ |
||||
|
add |
||||
|
{ |
||||
|
lock (_lock) |
||||
|
{ |
||||
|
_tick += value; |
||||
|
_count++; |
||||
|
|
||||
|
if (_count == 1) |
||||
|
{ |
||||
|
_choreographer.Task.Result.PostFrameCallback(this); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
remove |
||||
|
{ |
||||
|
lock (_lock) |
||||
|
{ |
||||
|
_tick -= value; |
||||
|
_count--; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
internal IDisposable SubscribeView(AvaloniaView view) |
||||
|
{ |
||||
|
lock (_lock) |
||||
|
{ |
||||
|
_views.Add(view); |
||||
|
|
||||
|
if (_views.Count == 1) |
||||
|
{ |
||||
|
_choreographer.Task.Result.PostFrameCallback(this); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return Disposable.Create( |
||||
|
() => |
||||
|
{ |
||||
|
lock (_lock) |
||||
|
{ |
||||
|
_views.Remove(view); |
||||
|
} |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
private void Loop() |
||||
|
{ |
||||
|
Looper.Prepare(); |
||||
|
_choreographer.SetResult(Choreographer.Instance); |
||||
|
Looper.Loop(); |
||||
|
} |
||||
|
|
||||
|
public void DoFrame(long frameTimeNanos) |
||||
|
{ |
||||
|
_tick?.Invoke(TimeSpan.FromTicks(frameTimeNanos / 100)); |
||||
|
|
||||
|
lock (_lock) |
||||
|
{ |
||||
|
if (_count > 0 && _views.Count > 0) |
||||
|
{ |
||||
|
Choreographer.Instance.PostFrameCallback(this); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,12 +1,21 @@ |
|||||
using System; |
|
||||
using Avalonia.Input; |
using Avalonia.Input; |
||||
using Avalonia.Platform; |
using Avalonia.Platform; |
||||
|
|
||||
namespace Avalonia.Android |
namespace Avalonia.Android |
||||
{ |
{ |
||||
internal class CursorFactory : IStandardCursorFactory |
internal class CursorFactory : ICursorFactory |
||||
{ |
{ |
||||
public IPlatformHandle GetCursor(StandardCursorType cursorType) |
public ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) => CursorImpl.ZeroCursor; |
||||
=> new PlatformHandle(IntPtr.Zero, "ZeroCursor"); |
|
||||
|
public ICursorImpl GetCursor(StandardCursorType cursorType) => CursorImpl.ZeroCursor; |
||||
|
|
||||
|
private sealed class CursorImpl : ICursorImpl |
||||
|
{ |
||||
|
public static CursorImpl ZeroCursor { get; } = new CursorImpl(); |
||||
|
|
||||
|
private CursorImpl() { } |
||||
|
|
||||
|
public void Dispose() { } |
||||
|
} |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -1,23 +1,30 @@ |
|||||
using Avalonia.OpenGL.Egl; |
using System; |
||||
|
|
||||
|
using Avalonia.OpenGL.Egl; |
||||
using Avalonia.OpenGL.Surfaces; |
using Avalonia.OpenGL.Surfaces; |
||||
|
|
||||
namespace Avalonia.Android.OpenGL |
namespace Avalonia.Android.OpenGL |
||||
{ |
{ |
||||
internal sealed class GlRenderTarget : EglPlatformSurfaceRenderTargetBase |
internal sealed class GlRenderTarget : EglPlatformSurfaceRenderTargetBase, IGlPlatformSurfaceRenderTargetWithCorruptionInfo |
||||
{ |
{ |
||||
private readonly EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo _info; |
private readonly EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo _info; |
||||
private readonly EglSurface _surface; |
private readonly EglSurface _surface; |
||||
|
private readonly IntPtr _handle; |
||||
|
|
||||
public GlRenderTarget( |
public GlRenderTarget( |
||||
EglPlatformOpenGlInterface egl, |
EglPlatformOpenGlInterface egl, |
||||
EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo info, |
EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo info, |
||||
EglSurface surface) |
EglSurface surface, |
||||
|
IntPtr handle) |
||||
: base(egl) |
: base(egl) |
||||
{ |
{ |
||||
_info = info; |
_info = info; |
||||
_surface = surface; |
_surface = surface; |
||||
|
_handle = handle; |
||||
} |
} |
||||
|
|
||||
|
public bool IsCorrupted => _handle != _info.Handle; |
||||
|
|
||||
public override IGlPlatformSurfaceRenderingSession BeginDraw() => BeginDraw(_surface, _info); |
public override IGlPlatformSurfaceRenderingSession BeginDraw() => BeginDraw(_surface, _info); |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -1,14 +0,0 @@ |
|||||
using Avalonia.Input; |
|
||||
|
|
||||
namespace Avalonia.Android.Platform.Input |
|
||||
{ |
|
||||
public class AndroidMouseDevice : MouseDevice |
|
||||
{ |
|
||||
public static AndroidMouseDevice Instance { get; } = new AndroidMouseDevice(); |
|
||||
|
|
||||
public AndroidMouseDevice() |
|
||||
{ |
|
||||
|
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,11 +0,0 @@ |
|||||
<?xml version="1.0" encoding="utf-8"?> |
|
||||
<configuration> |
|
||||
<runtime> |
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> |
|
||||
<dependentAssembly> |
|
||||
<assemblyIdentity name="System.Runtime.InteropServices.WindowsRuntime" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" /> |
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.1.0" newVersion="4.0.1.0" /> |
|
||||
</dependentAssembly> |
|
||||
</assemblyBinding> |
|
||||
</runtime> |
|
||||
</configuration> |
|
||||
@ -1,6 +1,6 @@ |
|||||
<?xml version="1.0" encoding="utf-8"?> |
<?xml version="1.0" encoding="utf-8"?> |
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="Avalonia.AndroidTestApplication" android:versionCode="1" android:versionName="1.0" android:installLocation="auto"> |
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="Avalonia.AndroidTestApplication" android:versionCode="1" android:versionName="1.0" android:installLocation="auto"> |
||||
<uses-sdk android:targetSdkVersion="29" /> |
<uses-sdk android:targetSdkVersion="30" /> |
||||
<application android:label="Avalonia.AndroidTestApplication" android:icon="@drawable/Icon" android:hardwareAccelerated="true"></application> |
<application android:label="Avalonia.AndroidTestApplication" android:icon="@drawable/Icon" android:hardwareAccelerated="true"></application> |
||||
<uses-permission android:name="android.permission.INTERNET" /> |
<uses-permission android:name="android.permission.INTERNET" /> |
||||
</manifest> |
</manifest> |
||||
@ -0,0 +1,12 @@ |
|||||
|
using System; |
||||
|
using XamlX.Transform; |
||||
|
|
||||
|
namespace Avalonia.Build.Tasks |
||||
|
{ |
||||
|
public class DeterministicIdGenerator : IXamlIdentifierGenerator |
||||
|
{ |
||||
|
private int _nextId = 1; |
||||
|
|
||||
|
public string GenerateIdentifierPart() => (_nextId++).ToString(); |
||||
|
} |
||||
|
} |
||||
@ -1,7 +1,10 @@ |
|||||
Compat issues with assembly Avalonia.Controls: |
Compat issues with assembly Avalonia.Controls: |
||||
|
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.INativeMenuExporterEventsImplBridge.RaiseClosed()' is present in the implementation but not in the contract. |
||||
|
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.INativeMenuExporterEventsImplBridge.RaiseOpening()' is present in the implementation but not in the contract. |
||||
MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract. |
MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract. |
||||
MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Notifications.NotificationCard.CloseOnClickProperty' does not exist in the implementation but it does exist in the contract. |
MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Notifications.NotificationCard.CloseOnClickProperty' does not exist in the implementation but it does exist in the contract. |
||||
|
EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints Avalonia.Platform.ExtendClientAreaChromeHints.Default' is (System.Int32)2 in the implementation but (System.Int32)1 in the contract. |
||||
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.ICursorImpl)' is present in the implementation but not in the contract. |
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.ICursorImpl)' is present in the implementation but not in the contract. |
||||
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' is present in the contract but not in the implementation. |
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' is present in the contract but not in the implementation. |
||||
MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract. |
MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract. |
||||
Total Issues: 5 |
Total Issues: 7 |
||||
|
|||||
@ -0,0 +1,16 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Avalonia.Controls |
||||
|
{ |
||||
|
|
||||
|
[Obsolete("This class exists to maintain backwards compatiblity with existing code. Use NativeMenuItemSeparator instead")] |
||||
|
public class NativeMenuItemSeperator : NativeMenuItemSeparator |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public class NativeMenuItemSeparator : NativeMenuItemBase |
||||
|
{ |
||||
|
[Obsolete("This is a temporary hack to make our MenuItem recognize this as a separator, don't use", true)] |
||||
|
public string Header => "-"; |
||||
|
} |
||||
|
} |
||||
@ -1,10 +0,0 @@ |
|||||
using System; |
|
||||
|
|
||||
namespace Avalonia.Controls |
|
||||
{ |
|
||||
public class NativeMenuItemSeperator : NativeMenuItemBase |
|
||||
{ |
|
||||
[Obsolete("This is a temporary hack to make our MenuItem recognize this as a separator, don't use", true)] |
|
||||
public string Header => "-"; |
|
||||
} |
|
||||
} |
|
||||
@ -1 +1 @@ |
|||||
Subproject commit f3ca2028f4f64be3556a6afd22f192902de095e5 |
Subproject commit 9e90d34e97c766ba8dcb70128147fcded65d195a |
||||
@ -0,0 +1,95 @@ |
|||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using System.Reactive.Subjects; |
||||
|
using Avalonia.Controls.Templates; |
||||
|
using Avalonia.Data; |
||||
|
using Avalonia.Threading; |
||||
|
using Avalonia.UnitTests; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Avalonia.Controls.UnitTests |
||||
|
{ |
||||
|
public class NumericUpDownTests |
||||
|
{ |
||||
|
private static TestServices Services => TestServices.StyledWindow; |
||||
|
|
||||
|
[Fact] |
||||
|
public void Text_Validation() |
||||
|
{ |
||||
|
RunTest((control, textbox) => |
||||
|
{ |
||||
|
var exception = new InvalidCastException("failed validation"); |
||||
|
var textObservable = new BehaviorSubject<BindingNotification>(new BindingNotification(exception, BindingErrorType.DataValidationError)); |
||||
|
control.Bind(NumericUpDown.TextProperty, textObservable); |
||||
|
Dispatcher.UIThread.RunJobs(); |
||||
|
|
||||
|
Assert.True(DataValidationErrors.GetHasErrors(control)); |
||||
|
Assert.True(DataValidationErrors.GetErrors(control).SequenceEqual(new[] { exception })); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Value_Validation() |
||||
|
{ |
||||
|
RunTest((control, textbox) => |
||||
|
{ |
||||
|
var exception = new InvalidCastException("failed validation"); |
||||
|
var valueObservable = new BehaviorSubject<BindingNotification>(new BindingNotification(exception, BindingErrorType.DataValidationError)); |
||||
|
control.Bind(NumericUpDown.ValueProperty, valueObservable); |
||||
|
Dispatcher.UIThread.RunJobs(); |
||||
|
|
||||
|
Assert.True(DataValidationErrors.GetHasErrors(control)); |
||||
|
Assert.True(DataValidationErrors.GetErrors(control).SequenceEqual(new[] { exception })); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private void RunTest(Action<NumericUpDown, TextBox> test) |
||||
|
{ |
||||
|
using (UnitTestApplication.Start(Services)) |
||||
|
{ |
||||
|
var control = CreateControl(); |
||||
|
TextBox textBox = GetTextBox(control); |
||||
|
var window = new Window { Content = control }; |
||||
|
window.ApplyTemplate(); |
||||
|
window.Presenter.ApplyTemplate(); |
||||
|
Dispatcher.UIThread.RunJobs(); |
||||
|
test.Invoke(control, textBox); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private NumericUpDown CreateControl() |
||||
|
{ |
||||
|
var control = new NumericUpDown |
||||
|
{ |
||||
|
Template = CreateTemplate() |
||||
|
}; |
||||
|
|
||||
|
control.ApplyTemplate(); |
||||
|
return control; |
||||
|
} |
||||
|
private TextBox GetTextBox(NumericUpDown control) |
||||
|
{ |
||||
|
return control.GetTemplateChildren() |
||||
|
.OfType<ButtonSpinner>() |
||||
|
.Select(b => b.Content) |
||||
|
.OfType<TextBox>() |
||||
|
.First(); |
||||
|
} |
||||
|
private IControlTemplate CreateTemplate() |
||||
|
{ |
||||
|
return new FuncControlTemplate<NumericUpDown>((control, scope) => |
||||
|
{ |
||||
|
var textBox = |
||||
|
new TextBox |
||||
|
{ |
||||
|
Name = "PART_TextBox" |
||||
|
}.RegisterInNameScope(scope); |
||||
|
return new ButtonSpinner |
||||
|
{ |
||||
|
Name = "PART_Spinner", |
||||
|
Content = textBox, |
||||
|
}.RegisterInNameScope(scope); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue