diff --git a/.ncrunch/Avalonia.Base.UnitTests.net461.v3.ncrunchproject b/.ncrunch/Avalonia.Base.UnitTests.net461.v3.ncrunchproject new file mode 100644 index 0000000000..6800b4a3fe --- /dev/null +++ b/.ncrunch/Avalonia.Base.UnitTests.net461.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Controls.UnitTests.net461.v3.ncrunchproject b/.ncrunch/Avalonia.Controls.UnitTests.net461.v3.ncrunchproject new file mode 100644 index 0000000000..6800b4a3fe --- /dev/null +++ b/.ncrunch/Avalonia.Controls.UnitTests.net461.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Input.UnitTests.net461.v3.ncrunchproject b/.ncrunch/Avalonia.Input.UnitTests.net461.v3.ncrunchproject new file mode 100644 index 0000000000..6800b4a3fe --- /dev/null +++ b/.ncrunch/Avalonia.Input.UnitTests.net461.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Interactivity.UnitTests.net461.v3.ncrunchproject b/.ncrunch/Avalonia.Interactivity.UnitTests.net461.v3.ncrunchproject new file mode 100644 index 0000000000..6800b4a3fe --- /dev/null +++ b/.ncrunch/Avalonia.Interactivity.UnitTests.net461.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Interactivity.UnitTests.netcoreapp1.1.v3.ncrunchproject b/.ncrunch/Avalonia.Interactivity.UnitTests.netcoreapp1.1.v3.ncrunchproject new file mode 100644 index 0000000000..6800b4a3fe --- /dev/null +++ b/.ncrunch/Avalonia.Interactivity.UnitTests.netcoreapp1.1.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Layout.UnitTests.net461.v3.ncrunchproject b/.ncrunch/Avalonia.Layout.UnitTests.net461.v3.ncrunchproject new file mode 100644 index 0000000000..6800b4a3fe --- /dev/null +++ b/.ncrunch/Avalonia.Layout.UnitTests.net461.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Markup.UnitTests.net461.v3.ncrunchproject b/.ncrunch/Avalonia.Markup.UnitTests.net461.v3.ncrunchproject new file mode 100644 index 0000000000..6800b4a3fe --- /dev/null +++ b/.ncrunch/Avalonia.Markup.UnitTests.net461.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Markup.Xaml.UnitTests.net461.v3.ncrunchproject b/.ncrunch/Avalonia.Markup.Xaml.UnitTests.net461.v3.ncrunchproject new file mode 100644 index 0000000000..6800b4a3fe --- /dev/null +++ b/.ncrunch/Avalonia.Markup.Xaml.UnitTests.net461.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Styling.UnitTests.net461.v3.ncrunchproject b/.ncrunch/Avalonia.Styling.UnitTests.net461.v3.ncrunchproject new file mode 100644 index 0000000000..6800b4a3fe --- /dev/null +++ b/.ncrunch/Avalonia.Styling.UnitTests.net461.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.UnitTests.net461.v3.ncrunchproject b/.ncrunch/Avalonia.UnitTests.net461.v3.ncrunchproject new file mode 100644 index 0000000000..6800b4a3fe --- /dev/null +++ b/.ncrunch/Avalonia.UnitTests.net461.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/.ncrunch/Direct3DInteropSample.v3.ncrunchproject b/.ncrunch/Direct3DInteropSample.v3.ncrunchproject new file mode 100644 index 0000000000..6800b4a3fe --- /dev/null +++ b/.ncrunch/Direct3DInteropSample.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/Avalonia.sln.DotSettings b/Avalonia.sln.DotSettings index ab21d6e50b..1fd6f8d092 100644 --- a/Avalonia.sln.DotSettings +++ b/Avalonia.sln.DotSettings @@ -1,4 +1,5 @@  + ExplicitlyExcluded ExplicitlyExcluded ExplicitlyExcluded HINT diff --git a/appveyor.yml b/appveyor.yml index 4bf7f7f157..6b63176a89 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -35,6 +35,7 @@ test: off artifacts: - path: artifacts\nuget\*.nupkg - path: artifacts\zip\*.zip + - path: artifacts\inspectcode.xml cache: - gtk-sharp-2.12.26.msi - dotnet-1.0.1.exe diff --git a/build.cake b/build.cake index 1e29b33fc1..6518431959 100644 --- a/build.cake +++ b/build.cake @@ -5,7 +5,8 @@ #addin "nuget:?package=Polly&version=4.2.0" #addin "nuget:?package=NuGet.Core&version=2.12.0" #tool "nuget:https://dotnet.myget.org/F/nuget-build/?package=NuGet.CommandLine&version=4.3.0-preview1-3980&prerelease" -#tool "nuget:?package=JetBrains.dotMemoryUnit&version=2.1.20150828.125449" +#tool "nuget:?package=JetBrains.dotMemoryUnit&version=2.3.20160517.113140" +#tool "JetBrains.ReSharper.CommandLineTools" /////////////////////////////////////////////////////////////////////////////// // TOOLS /////////////////////////////////////////////////////////////////////////////// @@ -104,7 +105,7 @@ Task("Restore-NuGet-Packages") .Does(() => { var maxRetryCount = 5; - var toolTimeout = 1d; + var toolTimeout = 2d; Policy .Handle() .Retry(maxRetryCount, (exception, retryCount, context) => { @@ -279,11 +280,15 @@ Task("Zip-Files") Zip(parameters.ZipSourceControlCatalogDesktopDirs, parameters.ZipTargetControlCatalogDesktopDirs, GetFiles(parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.dll") + + GetFiles(parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.config") + + GetFiles(parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.so") + + GetFiles(parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.dylib") + GetFiles(parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.exe")); }); Task("Create-NuGet-Packages") .IsDependentOn("Run-Unit-Tests") + .IsDependentOn("Inspect") .Does(() => { foreach(var nuspec in packages.NuspecNuGetSettings) @@ -331,7 +336,6 @@ Task("Publish-NuGet") .WithCriteria(() => !parameters.IsLocalBuild) .WithCriteria(() => !parameters.IsPullRequest) .WithCriteria(() => parameters.IsMainRepo) - .WithCriteria(() => parameters.IsMasterBranch) .WithCriteria(() => parameters.IsNuGetRelease) .Does(() => { @@ -360,6 +364,39 @@ Task("Publish-NuGet") Information("Publish-NuGet Task failed, but continuing with next Task..."); }); +Task("Inspect") + .WithCriteria(parameters.IsRunningOnWindows) + .IsDependentOn("Restore-NuGet-Packages") + .Does(() => + { + var badIssues = new []{"PossibleNullReferenceException"}; + var whitelist = new []{"tests", "src\\android", "src\\ios", + "src\\windows\\avalonia.designer", "src\\avalonia.htmlrenderer\\external"}; + Information("Running code inspections"); + + + StartProcess("tools\\JetBrains.ReSharper.CommandLineTools\\tools\\inspectcode.exe", + new ProcessSettings{ Arguments = "--output=artifacts\\inspectcode.xml --profile=Avalonia.sln.DotSettings Avalonia.sln" }); + Information("Analyzing report"); + var doc = XDocument.Parse(System.IO.File.ReadAllText("artifacts\\inspectcode.xml")); + var failBuild = false; + foreach(var xml in doc.Descendants("Issue")) + { + var typeId = xml.Attribute("TypeId").Value.ToString(); + if(badIssues.Contains(typeId)) + { + var file = xml.Attribute("File").Value.ToString().ToLower(); + if(whitelist.Any(wh => file.StartsWith(wh))) + continue; + var line = xml.Attribute("Line").Value.ToString(); + Error(typeId + " - " + file + " on line " + line); + failBuild = true; + } + } + if(failBuild) + throw new Exception("Issues found"); + }); + /////////////////////////////////////////////////////////////////////////////// // TARGETS /////////////////////////////////////////////////////////////////////////////// diff --git a/build/SkiaSharp.Desktop.props b/build/SkiaSharp.Desktop.props deleted file mode 100644 index bb6b29cfa6..0000000000 --- a/build/SkiaSharp.Desktop.props +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/build/SkiaSharp.props b/build/SkiaSharp.props index bd6b4ebc53..77407f9996 100644 --- a/build/SkiaSharp.props +++ b/build/SkiaSharp.props @@ -1,5 +1,6 @@  - + + diff --git a/docs/guidelines/build.md b/docs/guidelines/build.md index 12047ff357..822496814b 100644 --- a/docs/guidelines/build.md +++ b/docs/guidelines/build.md @@ -2,7 +2,7 @@ ## Windows -Avalonia requires at least Visual Studio 2015 to build on Windows. +Avalonia requires at least Visual Studio 2017 to build on Windows. ### Install GTK Sharp diff --git a/docs/index.md b/docs/index.md index 99424e3f75..37d95b203d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -10,7 +10,7 @@ What does alpha mean? Well, it means that it's now at a stage where you can have ## How do I try it out -The easiest way to try out Avalonia is to install the [Visual Studio Extension](https://visualstudiogallery.msdn.microsoft.com/a4542e8a-b56c-4295-8df1-7e220178b873). +The easiest way to try out Avalonia is to install the [Visual Studio Extension](https://marketplace.visualstudio.com/items?itemName=AvaloniaTeam.AvaloniaforVisualStudio). This will add a Avalonia project template and a Window template to the standard Visual Studo "Add" dialog (yes, icons still to come :) ): diff --git a/docs/tutorial/gettingstarted.md b/docs/tutorial/gettingstarted.md index 0972d26c48..5b7fe436a7 100644 --- a/docs/tutorial/gettingstarted.md +++ b/docs/tutorial/gettingstarted.md @@ -4,7 +4,7 @@ ![](images/add-dialogs.png) -The easiest way to try out Avalonia is to install the [Visual Studio Extension](https://visualstudiogallery.msdn.microsoft.com/e1c6ae1f-6fd9-467d-8f62-1e28b4225213). +The easiest way to try out Avalonia is to install the [Visual Studio Extension](https://marketplace.visualstudio.com/items?itemName=AvaloniaTeam.AvaloniaforVisualStudio). This will add a Avalonia project template and a Window template to the standard Visual Studo “Add” dialog (yes, icons still to come :) ): diff --git a/packages.cake b/packages.cake index f030c73a06..1e8e356694 100644 --- a/packages.cake +++ b/packages.cake @@ -7,6 +7,7 @@ public class Packages public FilePath[] BinFiles { get; private set; } public string NugetPackagesDir {get; private set;} public string SkiaSharpVersion {get; private set; } + public string SkiaSharpLinuxVersion {get; private set; } public Packages(ICakeContext context, Parameters parameters) { // NUGET NUSPECS @@ -75,6 +76,7 @@ public class Packages var SpracheVersion = packageVersions["Sprache"].FirstOrDefault().Item1; var SystemReactiveVersion = packageVersions["System.Reactive"].FirstOrDefault().Item1; SkiaSharpVersion = packageVersions["SkiaSharp"].FirstOrDefault().Item1; + SkiaSharpLinuxVersion = packageVersions["Avalonia.Skia.Linux.Natives"].FirstOrDefault().Item1; var SharpDXVersion = packageVersions["SharpDX"].FirstOrDefault().Item1; var SharpDXDirect2D1Version = packageVersions["SharpDX.Direct2D1"].FirstOrDefault().Item1; var SharpDXDirect3D11Version = packageVersions["SharpDX.Direct3D11"].FirstOrDefault().Item1; @@ -85,6 +87,7 @@ public class Packages context.Information("Package: Sprache, version: {0}", SpracheVersion); context.Information("Package: System.Reactive, version: {0}", SystemReactiveVersion); context.Information("Package: SkiaSharp, version: {0}", SkiaSharpVersion); + context.Information("Package: Avalonia.Skia.Linux.Natives, version: {0}", SkiaSharpLinuxVersion); context.Information("Package: SharpDX, version: {0}", SharpDXVersion); context.Information("Package: SharpDX.Direct2D1, version: {0}", SharpDXDirect2D1Version); context.Information("Package: SharpDX.Direct3D11, version: {0}", SharpDXDirect3D11Version); @@ -425,10 +428,7 @@ public class Packages { new NuSpecDependency() { Id = "Avalonia", Version = parameters.Version }, new NuSpecDependency() { Id = "SkiaSharp", Version = SkiaSharpVersion }, - //netstandard1.3 - new NuSpecDependency() { Id = "Avalonia", TargetFramework = "netstandard1.3", Version = parameters.Version }, - new NuSpecDependency() { Id = "SkiaSharp", TargetFramework = "netstandard1.3", Version = SkiaSharpVersion }, - new NuSpecDependency() { Id = "NETStandard.Library", TargetFramework = "netstandard1.3", Version = "1.6.0" } + new NuSpecDependency() { Id = "Avalonia.Skia.Linux.Natives", Version = SkiaSharpLinuxVersion } }, Files = new [] { @@ -446,11 +446,17 @@ public class Packages Id = "Avalonia.Desktop", Dependencies = new [] { - new NuSpecDependency() { Id = "Avalonia.Win32", Version = parameters.Version }, - new NuSpecDependency() { Id = "Avalonia.Direct2D1", Version = parameters.Version }, - new NuSpecDependency() { Id = "Avalonia.Gtk", Version = parameters.Version }, - new NuSpecDependency() { Id = "Avalonia.Cairo", Version = parameters.Version }, - new NuSpecDependency() { Id = "Avalonia.Skia.Desktop", Version = parameters.Version } + //Full .NET + new NuSpecDependency() { Id = "Avalonia.Direct2D1", TargetFramework="net45", Version = parameters.Version }, + new NuSpecDependency() { Id = "Avalonia.Gtk", TargetFramework="net45", Version = parameters.Version }, + new NuSpecDependency() { Id = "Avalonia.Cairo", TargetFramework="net45", Version = parameters.Version }, + new NuSpecDependency() { Id = "Avalonia.Win32", TargetFramework="net45", Version = parameters.Version }, + new NuSpecDependency() { Id = "Avalonia.Skia.Desktop", TargetFramework="net45", Version = parameters.Version }, + new NuSpecDependency() { Id = "Avalonia.Gtk3", TargetFramework="net45", Version = parameters.Version }, + //.NET Core + new NuSpecDependency() { Id = "Avalonia.Win32", TargetFramework="netcoreapp1.1", Version = parameters.Version }, + new NuSpecDependency() { Id = "Avalonia.Skia.Desktop", TargetFramework="netcoreapp1.1", Version = parameters.Version }, + new NuSpecDependency() { Id = "Avalonia.Gtk3", TargetFramework="netcoreapp1.1", Version = parameters.Version } }, Files = new NuSpecContent[] { diff --git a/parameters.cake b/parameters.cake index 4a7270011c..12f448303e 100644 --- a/parameters.cake +++ b/parameters.cake @@ -75,17 +75,25 @@ public class Parameters IsReleasable = StringComparer.OrdinalIgnoreCase.Equals(ReleasePlatform, Platform) && StringComparer.OrdinalIgnoreCase.Equals(ReleaseConfiguration, Configuration); IsMyGetRelease = !IsTagged && IsReleasable; - IsNuGetRelease = IsTagged && IsReleasable; + // VERSION Version = context.Argument("force-nuget-version", context.ParseAssemblyInfo(AssemblyInfoPath).AssemblyVersion); if (IsRunningOnAppVeyor) { + string tagVersion = null; if (IsTagged) { - // Use Tag Name as version - Version = buildSystem.AppVeyor.Environment.Repository.Tag.Name; + var tag = buildSystem.AppVeyor.Environment.Repository.Tag.Name; + var nugetReleasePrefix = "nuget-release-"; + IsNuGetRelease = IsTagged && IsReleasable && tag.StartsWith(nugetReleasePrefix); + if(IsNuGetRelease) + tagVersion = tag.Substring(nugetReleasePrefix.Length); + } + if(tagVersion != null) + { + Version = tagVersion; } else { diff --git a/readme.md b/readme.md index 2149aff386..621037e89e 100644 --- a/readme.md +++ b/readme.md @@ -42,7 +42,7 @@ using Direct2D and other operating systems using Gtk & Cairo. Avalonia is now in alpha. What does "alpha" mean? Well, it means that it's now at a stage where you can have a play and hopefully create simple applications. There's now a [Visual -Studio Extension](https://visualstudiogallery.msdn.microsoft.com/e1c6ae1f-6fd9-467d-8f62-1e28b4225213) +Studio Extension](https://marketplace.visualstudio.com/items?itemName=AvaloniaTeam.AvaloniaforVisualStudio) containing project and item templates that will help you get started, and there's an initial complement of controls. There's still a lot missing, and you *will* find bugs, and the API *will* change, but this represents the first time diff --git a/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj b/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj index 3ca8640d40..e215105149 100644 --- a/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj +++ b/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj @@ -26,7 +26,7 @@ 4 - AnyCPU + x86 pdbonly true bin\Release\ @@ -142,6 +142,7 @@ + \ No newline at end of file diff --git a/samples/interop/Direct3DInteropSample/MainWindow.cs b/samples/interop/Direct3DInteropSample/MainWindow.cs index 1ff1e1938b..ad40e81895 100644 --- a/samples/interop/Direct3DInteropSample/MainWindow.cs +++ b/samples/interop/Direct3DInteropSample/MainWindow.cs @@ -58,7 +58,7 @@ namespace Direct3DInteropSample new ModeDescription((int)ClientSize.Width, (int)ClientSize.Height, new Rational(60, 1), Format.R8G8B8A8_UNorm), IsWindowed = true, - OutputHandle = PlatformImpl.Handle.Handle, + OutputHandle = PlatformImpl?.Handle.Handle ?? IntPtr.Zero, SampleDescription = new SampleDescription(1, 0), SwapEffect = SwapEffect.Discard, Usage = Usage.RenderTargetOutput diff --git a/samples/interop/GtkInteropDemo/GtkInteropDemo.csproj b/samples/interop/GtkInteropDemo/GtkInteropDemo.csproj index 044b96c865..d9a61064f1 100644 --- a/samples/interop/GtkInteropDemo/GtkInteropDemo.csproj +++ b/samples/interop/GtkInteropDemo/GtkInteropDemo.csproj @@ -149,6 +149,7 @@ ControlCatalog + \ No newline at end of file diff --git a/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj b/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj index c787ab76ee..28e5e274d0 100644 --- a/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj +++ b/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj @@ -179,6 +179,7 @@ MSBuild:Compile + \ No newline at end of file diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs index 2089359c26..c779031e6c 100644 --- a/src/Android/Avalonia.Android/AndroidPlatform.cs +++ b/src/Android/Avalonia.Android/AndroidPlatform.cs @@ -53,6 +53,7 @@ namespace Avalonia.Android .Bind().ToSingleton() .Bind().ToSingleton() .Bind().ToConstant(Instance) + .Bind().ToConstant(ImmediateRenderer.Factory) .Bind().ToConstant(new AndroidThreadingInterface()) .Bind().ToTransient() .Bind().ToConstant(Instance) diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 7359dceae1..1bdf1eb5e3 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -578,13 +578,13 @@ namespace Avalonia if (notification == null) { - return TypeUtilities.CastOrDefault(value, type); + return TypeUtilities.ConvertImplicitOrDefault(value, type); } else { if (notification.HasValue) { - notification.SetValue(TypeUtilities.CastOrDefault(notification.Value, type)); + notification.SetValue(TypeUtilities.ConvertImplicitOrDefault(notification.Value, type)); } return notification; @@ -622,14 +622,9 @@ namespace Avalonia /// The default value. private object GetDefaultValue(AvaloniaProperty property) { - if (property.Inherits && _inheritanceParent != null) - { - return (_inheritanceParent as AvaloniaObject).GetValueInternal(property); - } - else - { - return ((IStyledPropertyAccessor)property).GetDefaultValue(GetType()); - } + if (property.Inherits && _inheritanceParent is AvaloniaObject aobj) + return aobj.GetValueInternal(property); + return ((IStyledPropertyAccessor) property).GetDefaultValue(GetType()); } /// @@ -735,7 +730,7 @@ namespace Avalonia ThrowNotRegistered(property); } - if (!TypeUtilities.TryCast(property.PropertyType, value, out value)) + if (!TypeUtilities.TryConvertImplicit(property.PropertyType, value, out value)) { throw new ArgumentException(string.Format( "Invalid value for Property '{0}': '{1}' ({2})", diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs index 61006b1173..fb78e3b2a0 100644 --- a/src/Avalonia.Base/AvaloniaProperty.cs +++ b/src/Avalonia.Base/AvaloniaProperty.cs @@ -476,7 +476,7 @@ namespace Avalonia /// True if the value is valid, otherwise false. public bool IsValidValue(object value) { - return TypeUtilities.TryCast(PropertyType, value, out value); + return TypeUtilities.TryConvertImplicit(PropertyType, value, out value); } /// diff --git a/src/Avalonia.Base/Collections/AvaloniaDictionary.cs b/src/Avalonia.Base/Collections/AvaloniaDictionary.cs index d63a5baf16..1aa239180c 100644 --- a/src/Avalonia.Base/Collections/AvaloniaDictionary.cs +++ b/src/Avalonia.Base/Collections/AvaloniaDictionary.cs @@ -103,11 +103,9 @@ namespace Avalonia.Collections _inner = new Dictionary(); - if (PropertyChanged != null) - { - PropertyChanged(this, new PropertyChangedEventArgs("Count")); - PropertyChanged(this, new PropertyChangedEventArgs($"Item[]")); - } + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Count")); + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"Item[]")); + if (CollectionChanged != null) { @@ -144,12 +142,9 @@ namespace Avalonia.Collections if (_inner.TryGetValue(key, out value)) { - if (PropertyChanged != null) - { - PropertyChanged(this, new PropertyChangedEventArgs("Count")); - PropertyChanged(this, new PropertyChangedEventArgs($"Item[{key}]")); - } - + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Count")); + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"Item[{key}]")); + if (CollectionChanged != null) { var e = new NotifyCollectionChangedEventArgs( @@ -199,11 +194,9 @@ namespace Avalonia.Collections private void NotifyAdd(TKey key, TValue value) { - if (PropertyChanged != null) - { - PropertyChanged(this, new PropertyChangedEventArgs("Count")); - PropertyChanged(this, new PropertyChangedEventArgs($"Item[{key}]")); - } + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Count")); + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"Item[{key}]")); + if (CollectionChanged != null) { diff --git a/src/Avalonia.Base/PriorityValue.cs b/src/Avalonia.Base/PriorityValue.cs index 57e2854014..c33d50ff0e 100644 --- a/src/Avalonia.Base/PriorityValue.cs +++ b/src/Avalonia.Base/PriorityValue.cs @@ -249,7 +249,7 @@ namespace Avalonia value = (notification.HasValue) ? notification.Value : null; } - if (TypeUtilities.TryCast(_valueType, value, out castValue)) + if (TypeUtilities.TryConvertImplicit(_valueType, value, out castValue)) { var old = _value; @@ -285,7 +285,7 @@ namespace Avalonia Property.Name, _valueType, value, - value.GetType()); + value?.GetType()); } } } diff --git a/src/Avalonia.Base/Utilities/TypeUtilities.cs b/src/Avalonia.Base/Utilities/TypeUtilities.cs index 7295bfa7ab..dd93811498 100644 --- a/src/Avalonia.Base/Utilities/TypeUtilities.cs +++ b/src/Avalonia.Base/Utilities/TypeUtilities.cs @@ -14,17 +14,61 @@ namespace Avalonia.Utilities /// public static class TypeUtilities { - private static readonly Dictionary> Conversions = new Dictionary>() + private static int[] Conversions = { - { typeof(decimal), new List { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(char) } }, - { typeof(double), new List { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(char), typeof(float) } }, - { typeof(float), new List { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(char), typeof(float) } }, - { typeof(ulong), new List { typeof(byte), typeof(ushort), typeof(uint), typeof(char) } }, - { typeof(long), new List { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(char) } }, - { typeof(uint), new List { typeof(byte), typeof(ushort), typeof(char) } }, - { typeof(int), new List { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(char) } }, - { typeof(ushort), new List { typeof(byte), typeof(char) } }, - { typeof(short), new List { typeof(byte) } } + 0b101111111111101, // Boolean + 0b100001111111110, // Char + 0b101111111111111, // SByte + 0b101111111111111, // Byte + 0b101111111111111, // Int16 + 0b101111111111111, // UInt16 + 0b101111111111111, // Int32 + 0b101111111111111, // UInt32 + 0b101111111111111, // Int64 + 0b101111111111111, // UInt64 + 0b101111111111101, // Single + 0b101111111111101, // Double + 0b101111111111101, // Decimal + 0b110000000000000, // DateTime + 0b111111111111111, // String + }; + + private static int[] ImplicitConversions = + { + 0b000000000000001, // Boolean + 0b001110111100010, // Char + 0b001110101010100, // SByte + 0b001111111111000, // Byte + 0b001110101010000, // Int16 + 0b001111111100000, // UInt16 + 0b001110101000000, // Int32 + 0b001111110000000, // UInt32 + 0b001110100000000, // Int64 + 0b001111000000000, // UInt64 + 0b000110000000000, // Single + 0b000100000000000, // Double + 0b001000000000000, // Decimal + 0b010000000000000, // DateTime + 0b100000000000000, // String + }; + + private static Type[] InbuiltTypes = + { + typeof(Boolean), + typeof(Char), + typeof(SByte), + typeof(Byte), + typeof(Int16), + typeof(UInt16), + typeof(Int32), + typeof(UInt32), + typeof(Int64), + typeof(UInt64), + typeof(Single), + typeof(Double), + typeof(Decimal), + typeof(DateTime), + typeof(String), }; private static readonly Type[] NumericTypes = new[] @@ -54,49 +98,104 @@ namespace Avalonia.Utilities } /// - /// Try to cast a value to a type, using implicit conversions if possible. + /// Try to convert a value to a type by any means possible. /// /// The type to cast to. /// The value to cast. + /// The culture to use. /// If sucessful, contains the cast value. /// True if the cast was sucessful, otherwise false. - public static bool TryCast(Type to, object value, out object result) + public static bool TryConvert(Type to, object value, CultureInfo culture, out object result) { - Contract.Requires(to != null); - if (value == null) { result = null; return AcceptsNull(to); } - var from = value.GetType(); - if (value == AvaloniaProperty.UnsetValue) { result = value; return true; } - else if (to.GetTypeInfo().IsAssignableFrom(from.GetTypeInfo())) + + var from = value.GetType(); + var fromTypeInfo = from.GetTypeInfo(); + var toTypeInfo = to.GetTypeInfo(); + + if (toTypeInfo.IsAssignableFrom(fromTypeInfo)) { result = value; return true; } - else if (Conversions.ContainsKey(to) && Conversions[to].Contains(from)) + + if (to == typeof(string)) { - result = Convert.ChangeType(value, to); + result = Convert.ToString(value); return true; } - else + + if (toTypeInfo.IsEnum && from == typeof(string)) + { + if (Enum.IsDefined(to, (string)value)) + { + result = Enum.Parse(to, (string)value); + return true; + } + } + + if (!fromTypeInfo.IsEnum && toTypeInfo.IsEnum) { - var cast = from.GetRuntimeMethods() - .FirstOrDefault(m => m.Name == "op_Implicit" && m.ReturnType == to); + result = null; + + if (TryConvert(Enum.GetUnderlyingType(to), value, culture, out object enumValue)) + { + result = Enum.ToObject(to, enumValue); + return true; + } + } - if (cast != null) + if (fromTypeInfo.IsEnum && IsNumeric(to)) + { + try { - result = cast.Invoke(null, new[] { value }); + result = Convert.ChangeType((int)value, to, culture); return true; } + catch + { + result = null; + return false; + } + } + + var convertableFrom = Array.IndexOf(InbuiltTypes, from); + var convertableTo = Array.IndexOf(InbuiltTypes, to); + + if (convertableFrom != -1 && convertableTo != -1) + { + if ((Conversions[convertableFrom] & 1 << convertableTo) != 0) + { + try + { + result = Convert.ChangeType(value, to, culture); + return true; + } + catch + { + result = null; + return false; + } + } + } + + var cast = from.GetRuntimeMethods() + .FirstOrDefault(m => (m.Name == "op_Implicit" || m.Name == "op_Explicit") && m.ReturnType == to); + + if (cast != null) + { + result = cast.Invoke(null, new[] { value }); + return true; } result = null; @@ -104,15 +203,14 @@ namespace Avalonia.Utilities } /// - /// Try to convert a value to a type, using if possible, - /// otherwise using . + /// Try to convert a value to a type using the implicit conversions allowed by the C# + /// language. /// /// The type to cast to. /// The value to cast. - /// The culture to use. /// If sucessful, contains the cast value. /// True if the cast was sucessful, otherwise false. - public static bool TryConvert(Type to, object value, CultureInfo culture, out object result) + public static bool TryConvertImplicit(Type to, object value, out object result) { if (value == null) { @@ -120,54 +218,44 @@ namespace Avalonia.Utilities return AcceptsNull(to); } - var from = value.GetType(); - if (value == AvaloniaProperty.UnsetValue) { result = value; return true; } - if (to.GetTypeInfo().IsAssignableFrom(from.GetTypeInfo())) - { - result = value; - return true; - } + var from = value.GetType(); + var fromTypeInfo = from.GetTypeInfo(); + var toTypeInfo = to.GetTypeInfo(); - if (to == typeof(string)) + if (toTypeInfo.IsAssignableFrom(fromTypeInfo)) { - result = Convert.ToString(value); + result = value; return true; } - if (to.GetTypeInfo().IsEnum && from == typeof(string)) - { - if (Enum.IsDefined(to, (string)value)) - { - result = Enum.Parse(to, (string)value); - return true; - } - } - - bool containsFrom = Conversions.ContainsKey(from); - bool containsTo = Conversions.ContainsKey(to); + var convertableFrom = Array.IndexOf(InbuiltTypes, from); + var convertableTo = Array.IndexOf(InbuiltTypes, to); - if ((containsFrom && containsTo) || (from == typeof(string) && containsTo)) + if (convertableFrom != -1 && convertableTo != -1) { - try - { - result = Convert.ChangeType(value, to, culture); - return true; - } - catch + if ((ImplicitConversions[convertableFrom] & 1 << convertableTo) != 0) { - result = null; - return false; + try + { + result = Convert.ChangeType(value, to, CultureInfo.InvariantCulture); + return true; + } + catch + { + result = null; + return false; + } } } var cast = from.GetRuntimeMethods() - .FirstOrDefault(m => (m.Name == "op_Implicit" || m.Name == "op_Explicit") && m.ReturnType == to); + .FirstOrDefault(m => m.Name == "op_Implicit" && m.ReturnType == to); if (cast != null) { @@ -180,29 +268,28 @@ namespace Avalonia.Utilities } /// - /// Casts a value to a type, returning the default for that type if the value could not be - /// cast. + /// Convert a value to a type by any means possible, returning the default for that type + /// if the value could not be converted. /// /// The value to cast. /// The type to cast to.. + /// The culture to use. /// A value of . - public static object CastOrDefault(object value, Type type) + public static object ConvertOrDefault(object value, Type type, CultureInfo culture) { - var typeInfo = type.GetTypeInfo(); - object result; + return TryConvert(type, value, culture, out object result) ? result : Default(type); + } - if (TypeUtilities.TryCast(type, value, out result)) - { - return result; - } - else if (typeInfo.IsValueType) - { - return Activator.CreateInstance(type); - } - else - { - return null; - } + /// + /// Convert a value to a type using the implicit conversions allowed by the C# language or + /// return the default for the type if the value could not be converted. + /// + /// The value to cast. + /// The type to cast to.. + /// A value of . + public static object ConvertImplicitOrDefault(object value, Type type) + { + return TryConvertImplicit(type, value, out object result) ? result : Default(type); } /// diff --git a/src/Avalonia.Controls/AppBuilderBase.cs b/src/Avalonia.Controls/AppBuilderBase.cs index 97de093a59..1610d33f9b 100644 --- a/src/Avalonia.Controls/AppBuilderBase.cs +++ b/src/Avalonia.Controls/AppBuilderBase.cs @@ -55,8 +55,7 @@ namespace Avalonia.Controls public Action AfterSetupCallback { get; private set; } = builder => { }; /// - /// Gets or sets a method to call before is called on the - /// . + /// Gets or sets a method to call before Startis called on the . /// public Action BeforeStartCallback { get; private set; } = builder => { }; @@ -94,8 +93,7 @@ namespace Avalonia.Controls protected TAppBuilder Self => (TAppBuilder) this; /// - /// Registers a callback to call before is called on the - /// . + /// Registers a callback to call before Start is called on the . /// /// The callback. /// An instance. @@ -129,6 +127,24 @@ namespace Avalonia.Controls Instance.Run(window); } + /// + /// Starts the application with the provided instance of . + /// + /// The window type. + /// Instance of type TMainWindow to use when starting the app + /// A delegate that will be called to create a data context for the window (optional). + public void Start(TMainWindow mainWindow, Func dataContextProvider = null) + where TMainWindow : Window + { + Setup(); + BeforeStartCallback(Self); + + if (dataContextProvider != null) + mainWindow.DataContext = dataContextProvider(); + mainWindow.Show(); + Instance.Run(mainWindow); + } + /// /// Sets up the platform-specific services for the application, but does not run it. /// diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs index 24e5872eee..b728655863 100644 --- a/src/Avalonia.Controls/Button.cs +++ b/src/Avalonia.Controls/Button.cs @@ -291,7 +291,7 @@ namespace Avalonia.Controls { var button = e.Sender as Button; var isDefault = (bool)e.NewValue; - var inputRoot = button.VisualRoot as IInputElement; + var inputRoot = button?.VisualRoot as IInputElement; if (inputRoot != null) { diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 516da813ef..27b046cf47 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -645,7 +645,7 @@ namespace Avalonia.Controls if (_focusAdorner != null) { - var adornerLayer = _focusAdorner.Parent as Panel; + var adornerLayer = (IPanel)_focusAdorner.Parent; adornerLayer.Children.Remove(_focusAdorner); _focusAdorner = null; } diff --git a/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs b/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs index 4688017187..b8d54fa67b 100644 --- a/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs +++ b/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs @@ -4,6 +4,7 @@ using Avalonia.Input; using Avalonia.Layout; using Avalonia.Platform; using Avalonia.Styling; +using JetBrains.Annotations; namespace Avalonia.Controls.Embedding { @@ -18,6 +19,7 @@ namespace Avalonia.Controls.Embedding { } + [CanBeNull] public new IEmbeddableWindowImpl PlatformImpl => (IEmbeddableWindowImpl) base.PlatformImpl; public void Prepare() @@ -39,8 +41,9 @@ namespace Avalonia.Controls.Embedding protected override Size MeasureOverride(Size availableSize) { - base.MeasureOverride(PlatformImpl.ClientSize); - return PlatformImpl.ClientSize; + var cs = PlatformImpl?.ClientSize ?? default(Size); + base.MeasureOverride(cs); + return cs; } private readonly NameScope _nameScope = new NameScope(); @@ -63,9 +66,6 @@ namespace Avalonia.Controls.Embedding public void Unregister(string name) => _nameScope.Unregister(name); Type IStyleable.StyleKey => typeof(EmbeddableControlRoot); - public void Dispose() - { - PlatformImpl.Dispose(); - } + public void Dispose() => PlatformImpl?.Dispose(); } } diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs index 5d12c9963f..aa209e0462 100644 --- a/src/Avalonia.Controls/ItemsControl.cs +++ b/src/Avalonia.Controls/ItemsControl.cs @@ -354,7 +354,7 @@ namespace Avalonia.Controls } var collection = sender as ICollection; - PseudoClasses.Set(":empty", collection.Count == 0); + PseudoClasses.Set(":empty", collection == null || collection.Count == 0); } /// diff --git a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs index 96190720ae..20602d5475 100644 --- a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs +++ b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs @@ -155,8 +155,7 @@ namespace Avalonia.Controls.Presenters case NotifyCollectionChangedAction.Add: CreateAndRemoveContainers(); - if (e.NewStartingIndex >= FirstIndex && - e.NewStartingIndex < NextIndex) + if (e.NewStartingIndex < NextIndex) { RecycleContainers(); } diff --git a/src/Avalonia.Controls/Primitives/PopupRoot.cs b/src/Avalonia.Controls/Primitives/PopupRoot.cs index 86c1b47521..a999e4ae37 100644 --- a/src/Avalonia.Controls/Primitives/PopupRoot.cs +++ b/src/Avalonia.Controls/Primitives/PopupRoot.cs @@ -9,6 +9,7 @@ using Avalonia.Layout; using Avalonia.Media; using Avalonia.Platform; using Avalonia.VisualTree; +using JetBrains.Annotations; namespace Avalonia.Controls.Primitives { @@ -49,6 +50,7 @@ namespace Avalonia.Controls.Primitives /// /// Gets the platform-specific window implementation. /// + [CanBeNull] public new IPopupImpl PlatformImpl => (IPopupImpl)base.PlatformImpl; /// @@ -65,10 +67,7 @@ namespace Avalonia.Controls.Primitives IVisual IHostedVisualTreeRoot.Host => Parent; /// - public void Dispose() - { - this.PlatformImpl.Dispose(); - } + public void Dispose() => PlatformImpl?.Dispose(); /// protected override void OnTemplateApplied(TemplateAppliedEventArgs e) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 9748e5e772..d2e8085d8c 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -720,7 +720,7 @@ namespace Avalonia.Controls if (pos < text.Length) { --pos; - if (pos > 0 && Text[pos - 1] == '\r' && Text[pos] == '\n') + if (pos > 0 && text[pos - 1] == '\r' && text[pos] == '\n') { --pos; } @@ -771,6 +771,9 @@ namespace Avalonia.Controls private string GetSelection() { + var text = Text; + if (string.IsNullOrEmpty(text)) + return ""; var selectionStart = SelectionStart; var selectionEnd = SelectionEnd; var start = Math.Min(selectionStart, selectionEnd); @@ -779,7 +782,7 @@ namespace Avalonia.Controls { return ""; } - return Text.Substring(start, end - start); + return text.Substring(start, end - start); } private int GetLine(int caretIndex, IList lines) diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index f3a6ab92d0..567ca80d6e 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -14,6 +14,7 @@ using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Styling; using Avalonia.VisualTree; +using JetBrains.Annotations; namespace Avalonia.Controls { @@ -92,25 +93,25 @@ namespace Avalonia.Controls var rendererFactory = TryGetService(dependencyResolver); Renderer = rendererFactory?.CreateRenderer(this, renderLoop); - PlatformImpl.SetInputRoot(this); + impl.SetInputRoot(this); - PlatformImpl.Closed = HandleClosed; - PlatformImpl.Input = HandleInput; - PlatformImpl.Paint = HandlePaint; - PlatformImpl.Resized = HandleResized; - PlatformImpl.ScalingChanged = HandleScalingChanged; + impl.Closed = HandleClosed; + impl.Input = HandleInput; + impl.Paint = HandlePaint; + impl.Resized = HandleResized; + impl.ScalingChanged = HandleScalingChanged; _keyboardNavigationHandler?.SetOwner(this); _accessKeyHandler?.SetOwner(this); styler?.ApplyStyles(this); - ClientSize = PlatformImpl.ClientSize; + ClientSize = impl.ClientSize; this.GetObservable(PointerOverElementProperty) .Select( x => (x as InputElement)?.GetObservable(CursorProperty) ?? Observable.Empty()) - .Switch().Subscribe(cursor => PlatformImpl.SetCursor(cursor?.PlatformCursor)); + .Switch().Subscribe(cursor => PlatformImpl?.SetCursor(cursor?.PlatformCursor)); if (_applicationLifecycle != null) { @@ -135,10 +136,8 @@ namespace Avalonia.Controls /// /// Gets the platform-specific window implementation. /// - public ITopLevelImpl PlatformImpl - { - get; - } + [CanBeNull] + public ITopLevelImpl PlatformImpl { get; private set; } /// /// Gets the renderer for the window. @@ -177,7 +176,7 @@ namespace Avalonia.Controls Size ILayoutRoot.MaxClientSize => Size.Infinity; /// - double ILayoutRoot.LayoutScaling => PlatformImpl.Scaling; + double ILayoutRoot.LayoutScaling => PlatformImpl?.Scaling ?? 1; IStyleHost IStyleHost.StylingParent { @@ -189,25 +188,27 @@ namespace Avalonia.Controls /// protected virtual IRenderTarget CreateRenderTarget() { + if(PlatformImpl == null) + throw new InvalidOperationException("Cann't create render target, PlatformImpl is null (might be already disposed)"); return _renderInterface.CreateRenderTarget(PlatformImpl.Surfaces); } /// void IRenderRoot.Invalidate(Rect rect) { - PlatformImpl.Invalidate(rect); + PlatformImpl?.Invalidate(rect); } /// Point IRenderRoot.PointToClient(Point p) { - return PlatformImpl.PointToClient(p); + return PlatformImpl?.PointToClient(p) ?? default(Point); } /// Point IRenderRoot.PointToScreen(Point p) { - return PlatformImpl.PointToScreen(p); + return PlatformImpl?.PointToScreen(p) ?? default(Point); } /// @@ -224,6 +225,8 @@ namespace Avalonia.Controls /// protected virtual void HandleClosed() { + PlatformImpl = null; + Closed?.Invoke(this, EventArgs.Empty); Renderer?.Dispose(); Renderer = null; diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 75587dcaec..32eaf499f0 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -12,6 +12,7 @@ using Avalonia.Platform; using Avalonia.Styling; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; namespace Avalonia.Controls { @@ -87,11 +88,11 @@ namespace Avalonia.Controls static Window() { BackgroundProperty.OverrideDefaultValue(typeof(Window), Brushes.White); - TitleProperty.Changed.AddClassHandler((s, e) => s.PlatformImpl.SetTitle((string)e.NewValue)); + TitleProperty.Changed.AddClassHandler((s, e) => s.PlatformImpl?.SetTitle((string)e.NewValue)); HasSystemDecorationsProperty.Changed.AddClassHandler( - (s, e) => s.PlatformImpl.SetSystemDecorations((bool) e.NewValue)); + (s, e) => s.PlatformImpl?.SetSystemDecorations((bool) e.NewValue)); - IconProperty.Changed.AddClassHandler((s, e) => s.PlatformImpl.SetIcon(((WindowIcon)e.NewValue).PlatformImpl)); + IconProperty.Changed.AddClassHandler((s, e) => s.PlatformImpl?.SetIcon(((WindowIcon)e.NewValue).PlatformImpl)); } /// @@ -109,7 +110,7 @@ namespace Avalonia.Controls public Window(IWindowImpl impl) : base(impl) { - _maxPlatformClientSize = this.PlatformImpl.MaxClientSize; + _maxPlatformClientSize = PlatformImpl?.MaxClientSize ?? default(Size); } /// @@ -129,6 +130,7 @@ namespace Avalonia.Controls /// /// Gets the platform-specific window implementation. /// + [CanBeNull] public new IWindowImpl PlatformImpl => (IWindowImpl)base.PlatformImpl; /// @@ -164,8 +166,12 @@ namespace Avalonia.Controls /// public WindowState WindowState { - get { return this.PlatformImpl.WindowState; } - set { this.PlatformImpl.WindowState = value; } + get { return PlatformImpl?.WindowState ?? WindowState.Normal; } + set + { + if (PlatformImpl != null) + PlatformImpl.WindowState = value; + } } /// @@ -189,7 +195,7 @@ namespace Avalonia.Controls public void Close() { s_windows.Remove(this); - PlatformImpl.Dispose(); + PlatformImpl?.Dispose(); IsVisible = false; } @@ -221,7 +227,7 @@ namespace Avalonia.Controls { using (BeginAutoSizing()) { - PlatformImpl.Hide(); + PlatformImpl?.Hide(); } IsVisible = false; @@ -240,7 +246,7 @@ namespace Avalonia.Controls using (BeginAutoSizing()) { - PlatformImpl.Show(); + PlatformImpl?.Show(); } } @@ -278,7 +284,7 @@ namespace Avalonia.Controls var activated = affectedWindows.Where(w => w.IsActive).FirstOrDefault(); SetIsEnabled(affectedWindows, false); - var modal = PlatformImpl.ShowDialog(); + var modal = PlatformImpl?.ShowDialog(); var result = new TaskCompletionSource(); Observable.FromEventPattern( @@ -287,7 +293,7 @@ namespace Avalonia.Controls .Take(1) .Subscribe(_ => { - modal.Dispose(); + modal?.Dispose(); SetIsEnabled(affectedWindows, true); activated?.Activate(); result.SetResult((TResult)_dialogResult); diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index 21c248db0c..1f484fd6cb 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -9,6 +9,7 @@ using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.Layout; using Avalonia.Platform; +using JetBrains.Annotations; namespace Avalonia.Controls { @@ -43,10 +44,10 @@ namespace Avalonia.Controls public WindowBase(IWindowBaseImpl impl, IAvaloniaDependencyResolver dependencyResolver) : base(impl, dependencyResolver) { - PlatformImpl.Activated = HandleActivated; - PlatformImpl.Deactivated = HandleDeactivated; - PlatformImpl.PositionChanged = HandlePositionChanged; - this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => PlatformImpl.Resize(x)); + impl.Activated = HandleActivated; + impl.Deactivated = HandleDeactivated; + impl.PositionChanged = HandlePositionChanged; + this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => PlatformImpl?.Resize(x)); } /// @@ -64,6 +65,7 @@ namespace Avalonia.Controls /// public event EventHandler PositionChanged; + [CanBeNull] public new IWindowBaseImpl PlatformImpl => (IWindowBaseImpl) base.PlatformImpl; /// @@ -80,8 +82,12 @@ namespace Avalonia.Controls /// public Point Position { - get { return PlatformImpl.Position; } - set { PlatformImpl.Position = value; } + get { return PlatformImpl?.Position ?? default(Point); } + set + { + if (PlatformImpl is IWindowBaseImpl impl) + impl.Position = value; + } } /// @@ -98,7 +104,7 @@ namespace Avalonia.Controls /// public void Activate() { - PlatformImpl.Activate(); + PlatformImpl?.Activate(); } /// @@ -110,7 +116,7 @@ namespace Avalonia.Controls try { - PlatformImpl.Hide(); + PlatformImpl?.Hide(); IsVisible = false; } finally @@ -131,7 +137,7 @@ namespace Avalonia.Controls EnsureInitialized(); IsVisible = true; LayoutManager.Instance.ExecuteInitialLayoutPass(this); - PlatformImpl.Show(); + PlatformImpl?.Show(); } finally { @@ -163,10 +169,10 @@ namespace Avalonia.Controls { using (BeginAutoSizing()) { - PlatformImpl.Resize(finalSize); + PlatformImpl?.Resize(finalSize); } - return base.ArrangeOverride(PlatformImpl.ClientSize); + return base.ArrangeOverride(PlatformImpl?.ClientSize ?? default(Size)); } /// @@ -174,7 +180,7 @@ namespace Avalonia.Controls /// protected void EnsureInitialized() { - if (!this.IsInitialized) + if (!IsInitialized) { var init = (ISupportInitialize)this; init.BeginInit(); @@ -268,12 +274,12 @@ namespace Avalonia.Controls /// /// Starts moving a window with left button being held. Should be called from left mouse button press event handler /// - public void BeginMoveDrag() => PlatformImpl.BeginMoveDrag(); + public void BeginMoveDrag() => PlatformImpl?.BeginMoveDrag(); /// /// Starts resizing a window. This function is used if an application has window resizing controls. /// Should be called from left mouse button press event handler /// - public void BeginResizeDrag(WindowEdge edge) => PlatformImpl.BeginResizeDrag(edge); + public void BeginResizeDrag(WindowEdge edge) => PlatformImpl?.BeginResizeDrag(edge); } } diff --git a/src/Avalonia.DesignerSupport/DesignerAssist.cs b/src/Avalonia.DesignerSupport/DesignerAssist.cs index c9ae89354c..8d30f3cf25 100644 --- a/src/Avalonia.DesignerSupport/DesignerAssist.cs +++ b/src/Avalonia.DesignerSupport/DesignerAssist.cs @@ -75,7 +75,7 @@ namespace Avalonia.DesignerSupport private static void SetScalingFactor(double factor) { PlatformManager.SetDesignerScalingFactor(factor); - s_currentWindow?.PlatformImpl.Resize(s_currentWindow.ClientSize); + s_currentWindow?.PlatformImpl?.Resize(s_currentWindow.ClientSize); } static Window s_currentWindow; @@ -149,6 +149,8 @@ namespace Avalonia.DesignerSupport s_currentWindow = window; window.Show(); Design.ApplyDesignerProperties(window, control); + // ReSharper disable once PossibleNullReferenceException + // Always not null at this point Api.OnWindowCreated?.Invoke(window.PlatformImpl.Handle.Handle); Api.OnResize?.Invoke(); } diff --git a/src/Avalonia.DotNetFrameworkRuntime/AppBuilder.cs b/src/Avalonia.DotNetFrameworkRuntime/AppBuilder.cs index fc7fdd2431..9a54cdbab0 100644 --- a/src/Avalonia.DotNetFrameworkRuntime/AppBuilder.cs +++ b/src/Avalonia.DotNetFrameworkRuntime/AppBuilder.cs @@ -82,7 +82,13 @@ namespace Avalonia private void LoadAssembliesInDirectory() { - foreach (var file in new FileInfo(Assembly.GetEntryAssembly().Location).Directory.EnumerateFiles("*.dll")) + var location = Assembly.GetEntryAssembly().Location; + if (string.IsNullOrWhiteSpace(location)) + return; + var dir = new FileInfo(location).Directory; + if (dir == null) + return; + foreach (var file in dir.EnumerateFiles("*.dll")) { try { diff --git a/src/Avalonia.Layout/Layoutable.cs b/src/Avalonia.Layout/Layoutable.cs index 5abd6c52f7..95a0dd625c 100644 --- a/src/Avalonia.Layout/Layoutable.cs +++ b/src/Avalonia.Layout/Layoutable.cs @@ -456,10 +456,9 @@ namespace Avalonia.Layout ApplyTemplate(); - var constrained = LayoutHelper - .ApplyLayoutConstraints(this, availableSize) - .Deflate(margin); - + var constrained = LayoutHelper.ApplyLayoutConstraints( + this, + availableSize.Deflate(margin)); var measured = MeasureOverride(constrained); var width = measured.Width; diff --git a/src/Avalonia.Styling/Controls/NameScope.cs b/src/Avalonia.Styling/Controls/NameScope.cs index 4c5875479e..8b5bd81d3c 100644 --- a/src/Avalonia.Styling/Controls/NameScope.cs +++ b/src/Avalonia.Styling/Controls/NameScope.cs @@ -50,7 +50,7 @@ namespace Avalonia.Controls return result; } - visual = (visual as ILogical).LogicalParent as Visual; + visual = (visual as ILogical)?.LogicalParent as Visual; } return null; diff --git a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs index af51eacaed..4879532d6e 100644 --- a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs @@ -20,6 +20,11 @@ namespace Avalonia.Rendering /// public class ImmediateRenderer : RendererBase, IRenderer, IVisualBrushRenderer { + class ImmediateRendererFactory : IRendererFactory + { + public IRenderer CreateRenderer(IRenderRoot root, IRenderLoop renderLoop) => new ImmediateRenderer(root); + } + private readonly IVisual _root; private readonly IRenderRoot _renderRoot; private IRenderTarget _renderTarget; @@ -36,6 +41,8 @@ namespace Avalonia.Rendering _renderRoot = root as IRenderRoot; } + public static IRendererFactory Factory { get; } = new ImmediateRendererFactory(); + /// public bool DrawFps { get; set; } diff --git a/src/Avalonia.Visuals/Rendering/ZIndexComparer.cs b/src/Avalonia.Visuals/Rendering/ZIndexComparer.cs index b9c43bcbc3..491541cd2e 100644 --- a/src/Avalonia.Visuals/Rendering/ZIndexComparer.cs +++ b/src/Avalonia.Visuals/Rendering/ZIndexComparer.cs @@ -8,6 +8,6 @@ namespace Avalonia.Rendering { public static readonly ZIndexComparer Instance = new ZIndexComparer(); - public int Compare(IVisual x, IVisual y) => x.ZIndex.CompareTo(y.ZIndex); + public int Compare(IVisual x, IVisual y) => (x?.ZIndex ?? 0).CompareTo(y?.ZIndex ?? 0); } } diff --git a/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs b/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs index 2af267aa97..11fbae4321 100644 --- a/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs +++ b/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs @@ -189,7 +189,7 @@ namespace Avalonia.VisualTree { Contract.Requires(visual != null); - return visual.VisualRoot as IRenderRoot ?? visual.VisualRoot; + return visual as IRenderRoot ?? visual.VisualRoot; } /// diff --git a/src/Gtk/Avalonia.Cairo/Media/DrawingContext.cs b/src/Gtk/Avalonia.Cairo/Media/DrawingContext.cs index 7d1776db0b..99b0a2ec73 100644 --- a/src/Gtk/Avalonia.Cairo/Media/DrawingContext.cs +++ b/src/Gtk/Avalonia.Cairo/Media/DrawingContext.cs @@ -9,6 +9,7 @@ using Avalonia.Cairo.Media.Imaging; using Avalonia.Media; using Avalonia.Platform; using Avalonia.Rendering; +// ReSharper disable PossibleNullReferenceException namespace Avalonia.Cairo.Media { diff --git a/src/Gtk/Avalonia.Cairo/Properties/AssemblyInfo.cs b/src/Gtk/Avalonia.Cairo/Properties/AssemblyInfo.cs index 63457cc1a2..dc7d815ddc 100644 --- a/src/Gtk/Avalonia.Cairo/Properties/AssemblyInfo.cs +++ b/src/Gtk/Avalonia.Cairo/Properties/AssemblyInfo.cs @@ -40,6 +40,6 @@ using System.Runtime.InteropServices; [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")] -[assembly: ExportRenderingSubsystem(OperatingSystemType.WinNT, 2, "Cairo", typeof(CairoPlatform), nameof(CairoPlatform.Initialize), RequiresWindowingSubsystem = "GTK")] -[assembly: ExportRenderingSubsystem(OperatingSystemType.Linux, 1, "Cairo", typeof(CairoPlatform), nameof(CairoPlatform.Initialize), RequiresWindowingSubsystem = "GTK")] -[assembly: ExportRenderingSubsystem(OperatingSystemType.OSX, 2, "Cairo", typeof(CairoPlatform), nameof(CairoPlatform.Initialize), RequiresWindowingSubsystem = "GTK")] +[assembly: ExportRenderingSubsystem(OperatingSystemType.WinNT, 3, "Cairo", typeof(CairoPlatform), nameof(CairoPlatform.Initialize), RequiresWindowingSubsystem = "GTK")] +[assembly: ExportRenderingSubsystem(OperatingSystemType.Linux, 2, "Cairo", typeof(CairoPlatform), nameof(CairoPlatform.Initialize), RequiresWindowingSubsystem = "GTK")] +[assembly: ExportRenderingSubsystem(OperatingSystemType.OSX, 3, "Cairo", typeof(CairoPlatform), nameof(CairoPlatform.Initialize), RequiresWindowingSubsystem = "GTK")] diff --git a/src/Gtk/Avalonia.Gtk/Properties/AssemblyInfo.cs b/src/Gtk/Avalonia.Gtk/Properties/AssemblyInfo.cs index f0e9ab4ccb..6d1eb24836 100644 --- a/src/Gtk/Avalonia.Gtk/Properties/AssemblyInfo.cs +++ b/src/Gtk/Avalonia.Gtk/Properties/AssemblyInfo.cs @@ -22,7 +22,7 @@ using System.Runtime.CompilerServices; // and "{Major}.{Minor}.{Build}.*" will update just the revision. [assembly: AssemblyVersion("1.0.*")] -[assembly: ExportWindowingSubsystem(OperatingSystemType.WinNT, 2, "GTK", typeof(GtkPlatform), nameof(GtkPlatform.Initialize))] -[assembly: ExportWindowingSubsystem(OperatingSystemType.Linux, 1, "GTK", typeof(GtkPlatform), nameof(GtkPlatform.Initialize))] -[assembly: ExportWindowingSubsystem(OperatingSystemType.OSX, 2, "GTK", typeof(GtkPlatform), nameof(GtkPlatform.Initialize))] +[assembly: ExportWindowingSubsystem(OperatingSystemType.WinNT, 3, "GTK", typeof(GtkPlatform), nameof(GtkPlatform.Initialize))] +[assembly: ExportWindowingSubsystem(OperatingSystemType.Linux, 2, "GTK", typeof(GtkPlatform), nameof(GtkPlatform.Initialize))] +[assembly: ExportWindowingSubsystem(OperatingSystemType.OSX, 3, "GTK", typeof(GtkPlatform), nameof(GtkPlatform.Initialize))] diff --git a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs index 0f54bb740f..a2913b4066 100644 --- a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs +++ b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs @@ -39,6 +39,7 @@ namespace Avalonia.Gtk3 .Bind().ToConstant(Instance) .Bind().ToSingleton() .Bind().ToConstant(new DefaultRenderLoop(60)) + .Bind().ToConstant(ImmediateRenderer.Factory) .Bind().ToConstant(new PlatformIconLoader()); } diff --git a/src/Gtk/Avalonia.Gtk3/Properties/AssemblyInfo.cs b/src/Gtk/Avalonia.Gtk3/Properties/AssemblyInfo.cs index 72e6388531..6b040240c4 100644 --- a/src/Gtk/Avalonia.Gtk3/Properties/AssemblyInfo.cs +++ b/src/Gtk/Avalonia.Gtk3/Properties/AssemblyInfo.cs @@ -2,6 +2,8 @@ using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using Avalonia.Gtk3; +using Avalonia.Platform; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information @@ -28,3 +30,6 @@ using System.Runtime.InteropServices; // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: ExportWindowingSubsystem(OperatingSystemType.WinNT, 2, "GTK3", typeof(Gtk3Platform), nameof(Gtk3Platform.Initialize))] +[assembly: ExportWindowingSubsystem(OperatingSystemType.Linux, 1, "GTK3", typeof(Gtk3Platform), nameof(Gtk3Platform.Initialize))] +[assembly: ExportWindowingSubsystem(OperatingSystemType.OSX, 2, "GTK3", typeof(Gtk3Platform), nameof(Gtk3Platform.Initialize))] \ No newline at end of file diff --git a/src/Gtk/Avalonia.Gtk3/SystemDialogs.cs b/src/Gtk/Avalonia.Gtk3/SystemDialogs.cs index efdadc2379..f6232ac68e 100644 --- a/src/Gtk/Avalonia.Gtk3/SystemDialogs.cs +++ b/src/Gtk/Avalonia.Gtk3/SystemDialogs.cs @@ -28,6 +28,7 @@ namespace Avalonia.Gtk3 List disposables = null; Action dispose = () => { + // ReSharper disable once PossibleNullReferenceException foreach (var d in disposables) d.Dispose(); disposables.Clear(); diff --git a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs index aaf8db9151..a65a91d6b0 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs @@ -35,6 +35,7 @@ namespace Avalonia.LinuxFramebuffer .Bind().ToConstant(KeyboardDevice) .Bind().ToConstant(MouseDevice) .Bind().ToSingleton() + .Bind().ToConstant(ImmediateRenderer.Factory) .Bind().ToConstant(PlatformThreadingInterface.Instance) .Bind().ToConstant(PlatformThreadingInterface.Instance); } diff --git a/src/Markup/Avalonia.Markup.Xaml/Parsers/SelectorParser.cs b/src/Markup/Avalonia.Markup.Xaml/Parsers/SelectorParser.cs index 8cf6f9794e..c4a7e188c6 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Parsers/SelectorParser.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Parsers/SelectorParser.cs @@ -68,7 +68,7 @@ namespace Avalonia.Markup.Xaml.Parsers } else if (property != null) { - var type = result.TargetType; + var type = result?.TargetType; if (type == null) { diff --git a/src/Markup/Avalonia.Markup/Data/Plugins/TaskStreamPlugin.cs b/src/Markup/Avalonia.Markup/Data/Plugins/TaskStreamPlugin.cs index d2c8c1b064..02fe8104b8 100644 --- a/src/Markup/Avalonia.Markup/Data/Plugins/TaskStreamPlugin.cs +++ b/src/Markup/Avalonia.Markup/Data/Plugins/TaskStreamPlugin.cs @@ -36,7 +36,7 @@ namespace Avalonia.Markup.Data.Plugins if (task != null) { - var resultProperty = task.GetType().GetTypeInfo().GetDeclaredProperty("Result"); + var resultProperty = task.GetType().GetRuntimeProperty("Result"); if (resultProperty != null) { @@ -61,7 +61,7 @@ namespace Avalonia.Markup.Data.Plugins protected IObservable HandleCompleted(Task task) { - var resultProperty = task.GetType().GetTypeInfo().GetDeclaredProperty("Result"); + var resultProperty = task.GetType().GetRuntimeProperty("Result"); if (resultProperty != null) { diff --git a/src/Markup/Avalonia.Markup/DefaultValueConverter.cs b/src/Markup/Avalonia.Markup/DefaultValueConverter.cs index 86d37d8e13..b56291a653 100644 --- a/src/Markup/Avalonia.Markup/DefaultValueConverter.cs +++ b/src/Markup/Avalonia.Markup/DefaultValueConverter.cs @@ -3,10 +3,7 @@ using System; using System.Globalization; -using System.Linq; -using System.Reflection; using Avalonia.Data; -using Avalonia.Logging; using Avalonia.Utilities; namespace Avalonia.Markup @@ -32,32 +29,28 @@ namespace Avalonia.Markup /// The converted value. public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - object result; - - if (value != null && - (TypeUtilities.TryConvert(targetType, value, culture, out result) || - TryConvertEnum(value, targetType, culture, out result))) + if (value == null) { - return result; + return AvaloniaProperty.UnsetValue; } - if (value != null) + if (TypeUtilities.TryConvert(targetType, value, culture, out object result)) { - string message; + return result; + } - if (TypeUtilities.IsNumeric(targetType)) - { - message = $"'{value}' is not a valid number."; - } - else - { - message = $"Could not convert '{value}' to '{targetType.Name}'."; - } + string message; - return new BindingNotification(new InvalidCastException(message), BindingErrorType.Error); + if (TypeUtilities.IsNumeric(targetType)) + { + message = $"'{value}' is not a valid number."; + } + else + { + message = $"Could not convert '{value}' to '{targetType.Name}'."; } - return AvaloniaProperty.UnsetValue; + return new BindingNotification(new InvalidCastException(message), BindingErrorType.Error); } /// @@ -72,34 +65,5 @@ namespace Avalonia.Markup { return Convert(value, targetType, parameter, culture); } - - private bool TryConvertEnum(object value, Type targetType, CultureInfo cultur, out object result) - { - var valueTypeInfo = value.GetType().GetTypeInfo(); - var targetTypeInfo = targetType.GetTypeInfo(); - - if (valueTypeInfo.IsEnum && !targetTypeInfo.IsEnum) - { - var enumValue = (int)value; - - if (TypeUtilities.TryCast(targetType, enumValue, out result)) - { - return true; - } - } - else if (!valueTypeInfo.IsEnum && targetTypeInfo.IsEnum) - { - object intValue; - - if (TypeUtilities.TryCast(typeof(int), value, out intValue)) - { - result = Enum.ToObject(targetType, intValue); - return true; - } - } - - result = null; - return false; - } } } diff --git a/src/Shared/SharedAssemblyInfo.cs b/src/Shared/SharedAssemblyInfo.cs index 63111eebf4..81c31e2687 100644 --- a/src/Shared/SharedAssemblyInfo.cs +++ b/src/Shared/SharedAssemblyInfo.cs @@ -13,6 +13,6 @@ using System.Resources; [assembly: AssemblyTrademark("")] [assembly: NeutralResourcesLanguage("en")] -[assembly: AssemblyVersion("0.4.1")] -[assembly: AssemblyFileVersion("0.4.1")] -[assembly: AssemblyInformationalVersion("0.4.1")] +[assembly: AssemblyVersion("0.5.1")] +[assembly: AssemblyFileVersion("0.5.1")] +[assembly: AssemblyInformationalVersion("0.5.1")] diff --git a/src/Skia/Avalonia.Skia.Desktop.NetStandard/Avalonia.Skia.Desktop.NetStandard.csproj b/src/Skia/Avalonia.Skia.Desktop.NetStandard/Avalonia.Skia.Desktop.NetStandard.csproj index 1a6b574e7b..ca9f6c76e1 100644 --- a/src/Skia/Avalonia.Skia.Desktop.NetStandard/Avalonia.Skia.Desktop.NetStandard.csproj +++ b/src/Skia/Avalonia.Skia.Desktop.NetStandard/Avalonia.Skia.Desktop.NetStandard.csproj @@ -40,7 +40,6 @@ - \ No newline at end of file diff --git a/src/Skia/Avalonia.Skia.Desktop/Avalonia.Skia.Desktop.csproj b/src/Skia/Avalonia.Skia.Desktop/Avalonia.Skia.Desktop.csproj index ca1c6508f2..b8dd79eca9 100644 --- a/src/Skia/Avalonia.Skia.Desktop/Avalonia.Skia.Desktop.csproj +++ b/src/Skia/Avalonia.Skia.Desktop/Avalonia.Skia.Desktop.csproj @@ -50,7 +50,7 @@ true true pdbonly - x86 + AnyCPU prompt MinimumRecommendedRules.ruleset diff --git a/src/Skia/Avalonia.Skia.Desktop/Properties/AssemblyInfo.cs b/src/Skia/Avalonia.Skia.Desktop/Properties/AssemblyInfo.cs index 69a3f34489..03ebc94a4c 100644 --- a/src/Skia/Avalonia.Skia.Desktop/Properties/AssemblyInfo.cs +++ b/src/Skia/Avalonia.Skia.Desktop/Properties/AssemblyInfo.cs @@ -37,4 +37,6 @@ using System.Runtime.InteropServices; [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")] -[assembly: ExportRenderingSubsystem(OperatingSystemType.WinNT, 3, "Skia", typeof(SkiaPlatform), nameof(SkiaPlatform.Initialize))] +[assembly: ExportRenderingSubsystem(OperatingSystemType.WinNT, 2, "Skia", typeof(SkiaPlatform), nameof(SkiaPlatform.Initialize))] +[assembly: ExportRenderingSubsystem(OperatingSystemType.OSX, 1, "Skia", typeof(SkiaPlatform), nameof(SkiaPlatform.Initialize))] +[assembly: ExportRenderingSubsystem(OperatingSystemType.Linux, 1, "Skia", typeof(SkiaPlatform), nameof(SkiaPlatform.Initialize))] \ No newline at end of file diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 1125863e72..4a9f2c6572 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -285,7 +285,7 @@ namespace Avalonia.Skia paint.StrokeCap = SKStrokeCap.Butt; if (pen.LineJoin == PenLineJoin.Miter) - paint.StrokeJoin = SKStrokeJoin.Mitter; + paint.StrokeJoin = SKStrokeJoin.Miter; else if (pen.LineJoin == PenLineJoin.Round) paint.StrokeJoin = SKStrokeJoin.Round; else @@ -397,7 +397,7 @@ namespace Avalonia.Skia public void PopOpacityMask() { - Canvas.SaveLayer(new SKPaint { XferMode = SKXferMode.DstIn }); + Canvas.SaveLayer(new SKPaint { BlendMode = SKBlendMode.DstIn }); using (var paintWrapper = maskStack.Pop()) { Canvas.DrawPaint(paintWrapper.Paint); diff --git a/src/Skia/Avalonia.Skia/FormattedTextImpl.cs b/src/Skia/Avalonia.Skia/FormattedTextImpl.cs index 133d9cd789..8568c80c04 100644 --- a/src/Skia/Avalonia.Skia/FormattedTextImpl.cs +++ b/src/Skia/Avalonia.Skia/FormattedTextImpl.cs @@ -42,7 +42,7 @@ namespace Avalonia.Skia _paint.Typeface = skiaTypeface; _paint.TextSize = (float)(typeface?.FontSize ?? 12); _paint.TextAlign = textAlignment.ToSKTextAlign(); - _paint.XferMode = SKXferMode.Src; + _paint.BlendMode = SKBlendMode.Src; _wrapping = wrapping; _constraint = constraint; diff --git a/src/Windows/Avalonia.Win32/Embedding/WinFormsAvaloniaControlHost.cs b/src/Windows/Avalonia.Win32/Embedding/WinFormsAvaloniaControlHost.cs index bdee85c91e..a484d6c0d2 100644 --- a/src/Windows/Avalonia.Win32/Embedding/WinFormsAvaloniaControlHost.cs +++ b/src/Windows/Avalonia.Win32/Embedding/WinFormsAvaloniaControlHost.cs @@ -15,7 +15,7 @@ namespace Avalonia.Win32.Embedding { private readonly EmbeddableControlRoot _root = new EmbeddableControlRoot(); - private IntPtr WindowHandle => ((WindowImpl) _root.PlatformImpl).Handle.Handle; + private IntPtr WindowHandle => ((WindowImpl) _root?.PlatformImpl)?.Handle?.Handle ?? IntPtr.Zero; public WinFormsAvaloniaControlHost() { @@ -25,6 +25,8 @@ namespace Avalonia.Win32.Embedding if (_root.IsFocused) FocusManager.Instance.Focus(null); _root.GotFocus += RootGotFocus; + // ReSharper disable once PossibleNullReferenceException + // Always non-null at this point _root.PlatformImpl.LostFocus += PlatformImpl_LostFocus; FixPosition(); } diff --git a/src/iOS/Avalonia.iOS/iOSPlatform.cs b/src/iOS/Avalonia.iOS/iOSPlatform.cs index 165b7d44f9..6d6e1fab03 100644 --- a/src/iOS/Avalonia.iOS/iOSPlatform.cs +++ b/src/iOS/Avalonia.iOS/iOSPlatform.cs @@ -58,6 +58,7 @@ namespace Avalonia.iOS .Bind().ToTransient() .Bind().ToConstant(KeyboardDevice) .Bind().ToConstant(MouseDevice) + .Bind().ToConstant(ImmediateRenderer.Factory) .Bind().ToSingleton() .Bind().ToConstant(PlatformThreadingInterface.Instance) .Bind().ToSingleton() diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs index 2f98cccadf..1da9cfce76 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs @@ -206,6 +206,30 @@ namespace Avalonia.Controls.UnitTests.Presenters Assert.Equal(expected, actual); } + [Fact] + public void Inserting_Items_Before_Visibile_Containers_Should_Update_Containers() + { + var target = CreateTarget(); + + target.ApplyTemplate(); + target.Measure(new Size(100, 100)); + target.Arrange(new Rect(0, 0, 100, 100)); + + ((ILogicalScrollable)target).Offset = new Vector(0, 5); + + var expected = Enumerable.Range(5, 10).Select(x => $"Item {x}").ToList(); + var items = (ObservableCollection)target.Items; + var actual = target.Panel.Children.Select(x => x.DataContext).ToList(); + + Assert.Equal(expected, actual); + + items.Insert(0, "Inserted"); + + expected = Enumerable.Range(4, 10).Select(x => $"Item {x}").ToList(); + actual = target.Panel.Children.Select(x => x.DataContext).ToList(); + Assert.Equal(expected, actual); + } + [Fact] public void Removing_First_Materialized_Item_Should_Update_Containers() { @@ -477,7 +501,6 @@ namespace Avalonia.Controls.UnitTests.Presenters target.Arrange(new Rect(0, 0, 100, 100)); var expected = Enumerable.Range(0, 6).Select(x => $"Item {x}").ToList(); - var items = (ObservableCollection)target.Items; var actual = target.Panel.Children.Select(x => x.DataContext).ToList(); Assert.Equal(expected, actual); diff --git a/tests/Avalonia.Layout.UnitTests/MeasureTests.cs b/tests/Avalonia.Layout.UnitTests/MeasureTests.cs index b5a3539da9..570628b5b7 100644 --- a/tests/Avalonia.Layout.UnitTests/MeasureTests.cs +++ b/tests/Avalonia.Layout.UnitTests/MeasureTests.cs @@ -100,5 +100,58 @@ namespace Avalonia.Layout.UnitTests Assert.Equal(0, target.DesiredSize.Height); } + + [Fact] + public void Margin_Should_Affect_AvailableSize() + { + MeasureTest target; + + var outer = new Decorator + { + Width = 100, + Height = 100, + Child = target = new MeasureTest + { + Margin = new Thickness(10), + } + }; + + outer.Measure(Size.Infinity); + + Assert.Equal(new Size(80, 80), target.AvailableSize); + } + + [Fact] + public void Margin_Should_Be_Applied_Before_Width_Height() + { + MeasureTest target; + + var outer = new Decorator + { + Width = 100, + Height = 100, + Child = target = new MeasureTest + { + Width = 80, + Height = 80, + Margin = new Thickness(10), + } + }; + + outer.Measure(Size.Infinity); + + Assert.Equal(new Size(80, 80), target.AvailableSize); + } + + class MeasureTest : Control + { + public Size? AvailableSize { get; private set; } + + protected override Size MeasureOverride(Size availableSize) + { + AvailableSize = availableSize; + return availableSize; + } + } } } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Parsers/SelectorParserTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Parsers/SelectorParserTests.cs new file mode 100644 index 0000000000..8c0b043907 --- /dev/null +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Parsers/SelectorParserTests.cs @@ -0,0 +1,17 @@ +using System; +using Avalonia.Controls; +using Avalonia.Markup.Xaml.Parsers; +using Xunit; + +namespace Avalonia.Markup.Xaml.UnitTests.Parsers +{ + public class SelectorParserTests + { + [Fact] + public void Parses_Boolean_Property_Selector() + { + var target = new SelectorParser((type, ns) => typeof(TextBlock)); + var result = target.Parse("TextBlock[IsPointerOver=True]"); + } + } +}