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"> |
|||
<ItemGroup> |
|||
<PackageReference Include="ReactiveUI" Version="12.1.1" /> |
|||
<PackageReference Include="ReactiveUI" Version="13.2.10" /> |
|||
</ItemGroup> |
|||
</Project> |
|||
|
|||
@ -1,5 +1,5 @@ |
|||
<?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"> |
|||
<uses-sdk android:targetSdkVersion="29" /> |
|||
<uses-sdk android:targetSdkVersion="30" /> |
|||
<application android:label="ControlCatalog.Android"></application> |
|||
</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.Platform; |
|||
|
|||
namespace Avalonia.Android |
|||
{ |
|||
internal class CursorFactory : IStandardCursorFactory |
|||
internal class CursorFactory : ICursorFactory |
|||
{ |
|||
public IPlatformHandle GetCursor(StandardCursorType cursorType) |
|||
=> new PlatformHandle(IntPtr.Zero, "ZeroCursor"); |
|||
public ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) => CursorImpl.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; |
|||
|
|||
namespace Avalonia.Android.OpenGL |
|||
{ |
|||
internal sealed class GlRenderTarget : EglPlatformSurfaceRenderTargetBase |
|||
internal sealed class GlRenderTarget : EglPlatformSurfaceRenderTargetBase, IGlPlatformSurfaceRenderTargetWithCorruptionInfo |
|||
{ |
|||
private readonly EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo _info; |
|||
private readonly EglSurface _surface; |
|||
private readonly IntPtr _handle; |
|||
|
|||
public GlRenderTarget( |
|||
EglPlatformOpenGlInterface egl, |
|||
EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo info, |
|||
EglSurface surface) |
|||
EglSurface surface, |
|||
IntPtr handle) |
|||
: base(egl) |
|||
{ |
|||
_info = info; |
|||
_surface = surface; |
|||
_handle = handle; |
|||
} |
|||
|
|||
public bool IsCorrupted => _handle != _info.Handle; |
|||
|
|||
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"?> |
|||
<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> |
|||
<uses-permission android:name="android.permission.INTERNET" /> |
|||
</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: |
|||
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 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.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. |
|||
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