diff --git a/Avalonia.sln b/Avalonia.sln index 3ed931933a..cf440972b2 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -1,4 +1,5 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 + +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.27130.2027 MinimumVisualStudioVersion = 10.0.40219.1 @@ -130,6 +131,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1 ProjectSection(SolutionItems) = preProject build\Base.props = build\Base.props build\Binding.props = build\Binding.props + build\BuildTargets.targets = build\BuildTargets.targets build\JetBrains.Annotations.props = build\JetBrains.Annotations.props build\JetBrains.dotMemoryUnit.props = build\JetBrains.dotMemoryUnit.props build\Magick.NET-Q16-AnyCPU.props = build\Magick.NET-Q16-AnyCPU.props @@ -147,7 +149,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1 build\Splat.props = build\Splat.props build\System.Memory.props = build\System.Memory.props build\XUnit.props = build\XUnit.props - build\BuildTargets.targets = build\BuildTargets.targets EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Targets", "Targets", "{4D6FAF79-58B4-482F-9122-0668C346364C}" @@ -189,11 +190,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia", "packages\Avalon EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Desktop", "src\Avalonia.Desktop\Avalonia.Desktop.csproj", "{3C471044-3640-45E3-B1B2-16D2FF8399EE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Build.Tasks", "src\Avalonia.Build.Tasks\Avalonia.Build.Tasks.csproj", "{BF28998D-072C-439A-AFBB-2FE5021241E0}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Build.Tasks", "src\Avalonia.Build.Tasks\Avalonia.Build.Tasks.csproj", "{BF28998D-072C-439A-AFBB-2FE5021241E0}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_build", "nukebuild\_build.csproj", "{3F00BC43-5095-477F-93D8-E65B08179A00}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Animation.UnitTests", "tests\Avalonia.Animation.UnitTests\Avalonia.Animation.UnitTests.csproj", "{AF227847-E65C-4BE9-BCE9-B551357788E0}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Animation.UnitTests", "tests\Avalonia.Animation.UnitTests\Avalonia.Animation.UnitTests.csproj", "{AF227847-E65C-4BE9-BCE9-B551357788E0}" EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution @@ -220,10 +221,6 @@ Global Release|iPhoneSimulator = Release|iPhoneSimulator EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {3F00BC43-5095-477F-93D8-E65B08179A00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3F00BC43-5095-477F-93D8-E65B08179A00}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3F00BC43-5095-477F-93D8-E65B08179A00}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3F00BC43-5095-477F-93D8-E65B08179A00}.Release|Any CPU.Build.0 = Release|Any CPU {B09B78D8-9B26-48B0-9149-D64A2F120F3F}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU {B09B78D8-9B26-48B0-9149-D64A2F120F3F}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU {B09B78D8-9B26-48B0-9149-D64A2F120F3F}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU @@ -1722,6 +1719,30 @@ Global {BF28998D-072C-439A-AFBB-2FE5021241E0}.Release|iPhone.Build.0 = Release|Any CPU {BF28998D-072C-439A-AFBB-2FE5021241E0}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {BF28998D-072C-439A-AFBB-2FE5021241E0}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.AppStore|Any CPU.ActiveCfg = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.AppStore|Any CPU.Build.0 = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.AppStore|iPhone.ActiveCfg = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.AppStore|iPhone.Build.0 = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Debug|iPhone.Build.0 = Debug|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Release|Any CPU.Build.0 = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Release|iPhone.ActiveCfg = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Release|iPhone.Build.0 = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {3F00BC43-5095-477F-93D8-E65B08179A00}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {AF227847-E65C-4BE9-BCE9-B551357788E0}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU {AF227847-E65C-4BE9-BCE9-B551357788E0}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU {AF227847-E65C-4BE9-BCE9-B551357788E0}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 8c5380e65e..fd40118888 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -38,6 +38,13 @@ jobs: inputs: version: '2.1.403' + - task: CmdLine@2 + displayName: 'Install Mono 5.18' + inputs: + script: | + curl -o ./mono.pkg https://download.mono-project.com/archive/5.18.0/macos-10-universal/MonoFramework-MDK-5.18.0.225.macos10.xamarin.universal.pkg + sudo installer -verbose -pkg ./mono.pkg -target / + - task: Xcode@5 inputs: actions: 'build' @@ -51,7 +58,9 @@ jobs: - task: CmdLine@2 displayName: 'Install CastXML' inputs: - script: brew install castxml + script: | + brew update + brew install castxml - task: CmdLine@2 displayName: 'Install Nuke' diff --git a/build/NetFX.props b/build/NetFX.props index d8b2daf13a..4d2841714b 100644 --- a/build/NetFX.props +++ b/build/NetFX.props @@ -3,4 +3,9 @@ /usr/lib/mono/4.6.1-api /Library/Frameworks/Mono.framework/Versions/Current/lib/mono/4.6.1-api + + /usr/lib/mono/4.7-api/ + /Library/Frameworks/Mono.framework/Versions/Current/lib/mono/4.7-api + + diff --git a/build/UnitTests.NetFX.props b/build/UnitTests.NetFX.props new file mode 100644 index 0000000000..e9a29d80fe --- /dev/null +++ b/build/UnitTests.NetFX.props @@ -0,0 +1,8 @@ + + + + + PreserveNewest + + + diff --git a/build/xunit.runner.mono.json b/build/xunit.runner.mono.json new file mode 100644 index 0000000000..4fb88b54c6 --- /dev/null +++ b/build/xunit.runner.mono.json @@ -0,0 +1 @@ +{ "appDomain": "denied" } diff --git a/global.json b/global.json index d11e78bd7f..6d12c28846 100644 --- a/global.json +++ b/global.json @@ -1,7 +1,7 @@ { "msbuild-sdks": { "Microsoft.Build.Traversal": "1.0.43", - "MSBuild.Sdk.Extras": "1.6.46", + "MSBuild.Sdk.Extras": "1.6.65", "AggregatePackage.NuGet.Sdk" : "0.1.12" } } diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs index a14842cfd3..bb31034299 100644 --- a/nukebuild/Build.cs +++ b/nukebuild/Build.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Threading; +using System.Xml.Linq; using Nuke.Common; using Nuke.Common.Git; using Nuke.Common.ProjectModel; @@ -56,6 +57,17 @@ partial class Build : NukeBuild Information("IsReleasable: " + Parameters.IsReleasable); Information("IsMyGetRelease: " + Parameters.IsMyGetRelease); Information("IsNuGetRelease: " + Parameters.IsNuGetRelease); + + void ExecWait(string preamble, string command, string args) + { + Console.WriteLine(preamble); + Process.Start(new ProcessStartInfo(command, args) {UseShellExecute = false}).WaitForExit(); + } + ExecWait("dotnet version:", "dotnet", "--version"); + if (Parameters.IsRunningOnUnix) + ExecWait("Mono version:", "mono", "--version"); + + } Target Clean => _ => _.Executes(() => @@ -92,16 +104,24 @@ partial class Build : NukeBuild ); }); - void RunCoreTest(string project, bool coreOnly = false) + void RunCoreTest(string project) { if(!project.EndsWith(".csproj")) project = System.IO.Path.Combine(project, System.IO.Path.GetFileName(project)+".csproj"); Information("Running tests from " + project); - var frameworks = new List(){"netcoreapp2.0"}; + XDocument xdoc; + using (var s = File.OpenRead(project)) + xdoc = XDocument.Load(s); + + List frameworks = null; + var targets = xdoc.Root.Descendants("TargetFrameworks").FirstOrDefault(); + if (targets != null) + frameworks = targets.Value.Split(';').Where(f => !string.IsNullOrWhiteSpace(f)).ToList(); + else + frameworks = new List {xdoc.Root.Descendants("TargetFramework").First().Value}; + foreach(var fw in frameworks) { - if(!fw.StartsWith("netcoreapp") && coreOnly) - continue; Information("Running for " + fw); DotNetTest(c => { @@ -124,18 +144,18 @@ partial class Build : NukeBuild .DependsOn(Compile) .Executes(() => { - RunCoreTest("./tests/Avalonia.Animation.UnitTests", false); - RunCoreTest("./tests/Avalonia.Base.UnitTests", false); - RunCoreTest("./tests/Avalonia.Controls.UnitTests", false); - RunCoreTest("./tests/Avalonia.Input.UnitTests", false); - RunCoreTest("./tests/Avalonia.Interactivity.UnitTests", false); - RunCoreTest("./tests/Avalonia.Layout.UnitTests", false); - RunCoreTest("./tests/Avalonia.Markup.UnitTests", false); - RunCoreTest("./tests/Avalonia.Markup.Xaml.UnitTests", false); - RunCoreTest("./tests/Avalonia.Styling.UnitTests", false); - RunCoreTest("./tests/Avalonia.Visuals.UnitTests", false); - RunCoreTest("./tests/Avalonia.Skia.UnitTests", false); - RunCoreTest("./tests/Avalonia.ReactiveUI.UnitTests", false); + RunCoreTest("./tests/Avalonia.Animation.UnitTests"); + RunCoreTest("./tests/Avalonia.Base.UnitTests"); + RunCoreTest("./tests/Avalonia.Controls.UnitTests"); + RunCoreTest("./tests/Avalonia.Input.UnitTests"); + RunCoreTest("./tests/Avalonia.Interactivity.UnitTests"); + RunCoreTest("./tests/Avalonia.Layout.UnitTests"); + RunCoreTest("./tests/Avalonia.Markup.UnitTests"); + RunCoreTest("./tests/Avalonia.Markup.Xaml.UnitTests"); + RunCoreTest("./tests/Avalonia.Styling.UnitTests"); + RunCoreTest("./tests/Avalonia.Visuals.UnitTests"); + RunCoreTest("./tests/Avalonia.Skia.UnitTests"); + RunCoreTest("./tests/Avalonia.ReactiveUI.UnitTests"); }); Target RunRenderTests => _ => _ @@ -143,9 +163,9 @@ partial class Build : NukeBuild .DependsOn(Compile) .Executes(() => { - RunCoreTest("./tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj", true); + RunCoreTest("./tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj"); if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - RunCoreTest("./tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj", true); + RunCoreTest("./tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj"); }); Target RunDesignerTests => _ => _ @@ -153,7 +173,7 @@ partial class Build : NukeBuild .DependsOn(Compile) .Executes(() => { - RunCoreTest("./tests/Avalonia.DesignerSupport.Tests", false); + RunCoreTest("./tests/Avalonia.DesignerSupport.Tests"); }); [PackageExecutable("JetBrains.dotMemoryUnit", "dotMemoryUnit.exe")] readonly Tool DotMemoryUnit; diff --git a/nukebuild/BuildParameters.cs b/nukebuild/BuildParameters.cs index 5f8ec7267e..afd1950859 100644 --- a/nukebuild/BuildParameters.cs +++ b/nukebuild/BuildParameters.cs @@ -14,13 +14,13 @@ using static Nuke.Common.Tools.MSBuild.MSBuildTasks; public partial class Build { [Parameter("configuration")] - public string NukeArgConfiguration { get; set; } + public string Configuration { get; set; } [Parameter("skip-tests")] - public bool NukeArgSkipTests { get; set; } + public bool SkipTests { get; set; } [Parameter("force-nuget-version")] - public string NukeArgForceNugetVersion { get; set; } + public string ForceNugetVersion { get; set; } public class BuildParameters { @@ -64,8 +64,8 @@ public partial class Build public BuildParameters(Build b) { // ARGUMENTS - Configuration = b.NukeArgConfiguration ?? "Release"; - SkipTests = b.NukeArgSkipTests; + Configuration = b.Configuration ?? "Release"; + SkipTests = b.SkipTests; // CONFIGURATION MainRepo = "https://github.com/AvaloniaUI/Avalonia"; @@ -102,7 +102,7 @@ public partial class Build IsNuGetRelease = IsMainRepo && IsReleasable && IsReleaseBranch; // VERSION - Version = b.NukeArgForceNugetVersion ?? GetVersion(); + Version = b.ForceNugetVersion ?? GetVersion(); if (IsRunningOnAzure) { diff --git a/src/Avalonia.Animation/AnimatorKeyFrame.cs b/src/Avalonia.Animation/AnimatorKeyFrame.cs index 78a9a1e412..36d15e518e 100644 --- a/src/Avalonia.Animation/AnimatorKeyFrame.cs +++ b/src/Avalonia.Animation/AnimatorKeyFrame.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel; +using Avalonia.Animation.Animators; using Avalonia.Data; using Avalonia.Reactive; diff --git a/src/Avalonia.Animation/IterationCount.cs b/src/Avalonia.Animation/IterationCount.cs index eafc483868..e9cd0686d8 100644 --- a/src/Avalonia.Animation/IterationCount.cs +++ b/src/Avalonia.Animation/IterationCount.cs @@ -44,7 +44,7 @@ namespace Avalonia.Animation { if (type > IterationType.Infinite) { - throw new ArgumentException("Invalid value", "type"); + throw new ArgumentException("Invalid value", nameof(type)); } _type = type; diff --git a/src/Avalonia.Base/Collections/AvaloniaDictionary.cs b/src/Avalonia.Base/Collections/AvaloniaDictionary.cs index e8dc2a5ed7..4de4c540a0 100644 --- a/src/Avalonia.Base/Collections/AvaloniaDictionary.cs +++ b/src/Avalonia.Base/Collections/AvaloniaDictionary.cs @@ -148,7 +148,7 @@ namespace Avalonia.Collections { if (_inner.TryGetValue(key, out TValue value)) { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Count")); + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count))); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"Item[{key}]")); if (CollectionChanged != null) @@ -209,7 +209,7 @@ namespace Avalonia.Collections private void NotifyAdd(TKey key, TValue value) { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Count")); + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count))); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"Item[{key}]")); diff --git a/src/Avalonia.Base/Collections/AvaloniaList.cs b/src/Avalonia.Base/Collections/AvaloniaList.cs index d5a4e63003..4d4a561b08 100644 --- a/src/Avalonia.Base/Collections/AvaloniaList.cs +++ b/src/Avalonia.Base/Collections/AvaloniaList.cs @@ -511,7 +511,7 @@ namespace Avalonia.Collections /// private void NotifyCountChanged() { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Count")); + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count))); } /// diff --git a/src/Avalonia.Base/Data/Core/ExpressionNode.cs b/src/Avalonia.Base/Data/Core/ExpressionNode.cs index f2f3ed9bfc..8a2dd46b86 100644 --- a/src/Avalonia.Base/Data/Core/ExpressionNode.cs +++ b/src/Avalonia.Base/Data/Core/ExpressionNode.cs @@ -147,6 +147,7 @@ namespace Avalonia.Data.Core private void StopListening() { StopListeningCore(); + _listening = false; } private BindingNotification TargetNullNotification() diff --git a/src/Avalonia.Base/Platform/IAssetLoader.cs b/src/Avalonia.Base/Platform/IAssetLoader.cs index 691fa2fc12..880ed62046 100644 --- a/src/Avalonia.Base/Platform/IAssetLoader.cs +++ b/src/Avalonia.Base/Platform/IAssetLoader.cs @@ -65,6 +65,7 @@ namespace Avalonia.Platform /// Gets all assets of a folder and subfolders that match specified uri. /// /// The URI. + /// The base URI. /// All matching assets as a tuple of the absolute path to the asset and the assembly containing the asset IEnumerable GetAssets(Uri uri, Uri baseUri); } diff --git a/src/Avalonia.Controls/Calendar/CalendarItem.cs b/src/Avalonia.Controls/Calendar/CalendarItem.cs index 577555333f..fb6dacaf81 100644 --- a/src/Avalonia.Controls/Calendar/CalendarItem.cs +++ b/src/Avalonia.Controls/Calendar/CalendarItem.cs @@ -52,7 +52,7 @@ namespace Avalonia.Controls.Primitives internal Calendar Owner { get; set; } internal CalendarDayButton CurrentButton { get; set; } - public static StyledProperty HeaderBackgroundProperty = Calendar.HeaderBackgroundProperty.AddOwner(); + public static readonly StyledProperty HeaderBackgroundProperty = Calendar.HeaderBackgroundProperty.AddOwner(); public IBrush HeaderBackground { get { return GetValue(HeaderBackgroundProperty); } diff --git a/src/Avalonia.Controls/Calendar/DatePicker.cs b/src/Avalonia.Controls/Calendar/DatePicker.cs index 2270598623..70de8bec16 100644 --- a/src/Avalonia.Controls/Calendar/DatePicker.cs +++ b/src/Avalonia.Controls/Calendar/DatePicker.cs @@ -954,7 +954,7 @@ namespace Avalonia.Controls } else { - var dateValidationError = new DatePickerDateValidationErrorEventArgs(new ArgumentOutOfRangeException("text", "SelectedDate value is not valid."), text); + var dateValidationError = new DatePickerDateValidationErrorEventArgs(new ArgumentOutOfRangeException(nameof(text), "SelectedDate value is not valid."), text); OnDateValidationError(dateValidationError); if (dateValidationError.ThrowException) diff --git a/src/Avalonia.Controls/GridLength.cs b/src/Avalonia.Controls/GridLength.cs index f6a608cd71..02be95b647 100644 --- a/src/Avalonia.Controls/GridLength.cs +++ b/src/Avalonia.Controls/GridLength.cs @@ -56,12 +56,12 @@ namespace Avalonia.Controls { if (value < 0 || double.IsNaN(value) || double.IsInfinity(value)) { - throw new ArgumentException("Invalid value", "value"); + throw new ArgumentException("Invalid value", nameof(value)); } if (type < GridUnitType.Auto || type > GridUnitType.Star) { - throw new ArgumentException("Invalid value", "type"); + throw new ArgumentException("Invalid value", nameof(type)); } _type = type; diff --git a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs index de68eb0ab0..57db1e19bb 100644 --- a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs +++ b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs @@ -487,7 +487,7 @@ namespace Avalonia.Controls { if (e == null) { - throw new ArgumentNullException("e"); + throw new ArgumentNullException(nameof(e)); } var handler = Spinned; diff --git a/src/Avalonia.Controls/Primitives/AdornerLayer.cs b/src/Avalonia.Controls/Primitives/AdornerLayer.cs index 5308a062ec..ed590679cd 100644 --- a/src/Avalonia.Controls/Primitives/AdornerLayer.cs +++ b/src/Avalonia.Controls/Primitives/AdornerLayer.cs @@ -13,7 +13,7 @@ namespace Avalonia.Controls.Primitives // TODO: Need to track position of adorned elements and move the adorner if they move. public class AdornerLayer : Panel, ICustomSimpleHitTest { - public static AttachedProperty AdornedElementProperty = + public static readonly AttachedProperty AdornedElementProperty = AvaloniaProperty.RegisterAttached("AdornedElement"); private static readonly AttachedProperty s_adornedElementInfoProperty = diff --git a/src/Avalonia.Controls/Viewbox.cs b/src/Avalonia.Controls/Viewbox.cs index db753f4ab4..3a070d07b2 100644 --- a/src/Avalonia.Controls/Viewbox.cs +++ b/src/Avalonia.Controls/Viewbox.cs @@ -12,7 +12,7 @@ namespace Avalonia.Controls /// /// The stretch property /// - public static AvaloniaProperty StretchProperty = + public static readonly AvaloniaProperty StretchProperty = AvaloniaProperty.RegisterDirect(nameof(Stretch), v => v.Stretch, (c, v) => c.Stretch = v, Stretch.Uniform); diff --git a/src/Avalonia.Input/Cursors.cs b/src/Avalonia.Input/Cursors.cs index 6f67f3b08c..d3618f30f3 100644 --- a/src/Avalonia.Input/Cursors.cs +++ b/src/Avalonia.Input/Cursors.cs @@ -47,7 +47,7 @@ namespace Avalonia.Input public class Cursor { - public static Cursor Default = new Cursor(StandardCursorType.Arrow); + public static readonly Cursor Default = new Cursor(StandardCursorType.Arrow); internal Cursor(IPlatformHandle platformCursor) { diff --git a/src/Avalonia.Input/DataFormats.cs b/src/Avalonia.Input/DataFormats.cs index 559d2cb643..cf5a6592e1 100644 --- a/src/Avalonia.Input/DataFormats.cs +++ b/src/Avalonia.Input/DataFormats.cs @@ -5,11 +5,11 @@ /// /// Dataformat for plaintext /// - public static string Text = nameof(Text); + public static readonly string Text = nameof(Text); /// /// Dataformat for one or more filenames /// - public static string FileNames = nameof(FileNames); + public static readonly string FileNames = nameof(FileNames); } -} \ No newline at end of file +} diff --git a/src/Avalonia.Input/DragDrop.cs b/src/Avalonia.Input/DragDrop.cs index b5aba55f6c..c58b764b1d 100644 --- a/src/Avalonia.Input/DragDrop.cs +++ b/src/Avalonia.Input/DragDrop.cs @@ -9,21 +9,21 @@ namespace Avalonia.Input /// /// Event which is raised, when a drag-and-drop operation enters the element. /// - public static RoutedEvent DragEnterEvent = RoutedEvent.Register("DragEnter", RoutingStrategies.Bubble, typeof(DragDrop)); + public static readonly RoutedEvent DragEnterEvent = RoutedEvent.Register("DragEnter", RoutingStrategies.Bubble, typeof(DragDrop)); /// /// Event which is raised, when a drag-and-drop operation leaves the element. /// - public static RoutedEvent DragLeaveEvent = RoutedEvent.Register("DragLeave", RoutingStrategies.Bubble, typeof(DragDrop)); + public static readonly RoutedEvent DragLeaveEvent = RoutedEvent.Register("DragLeave", RoutingStrategies.Bubble, typeof(DragDrop)); /// /// Event which is raised, when a drag-and-drop operation is updated while over the element. /// - public static RoutedEvent DragOverEvent = RoutedEvent.Register("DragOver", RoutingStrategies.Bubble, typeof(DragDrop)); + public static readonly RoutedEvent DragOverEvent = RoutedEvent.Register("DragOver", RoutingStrategies.Bubble, typeof(DragDrop)); /// /// Event which is raised, when a drag-and-drop operation should complete over the element. /// - public static RoutedEvent DropEvent = RoutedEvent.Register("Drop", RoutingStrategies.Bubble, typeof(DragDrop)); + public static readonly RoutedEvent DropEvent = RoutedEvent.Register("Drop", RoutingStrategies.Bubble, typeof(DragDrop)); - public static AvaloniaProperty AllowDropProperty = AvaloniaProperty.RegisterAttached("AllowDrop", typeof(DragDrop), inherits: true); + public static readonly AvaloniaProperty AllowDropProperty = AvaloniaProperty.RegisterAttached("AllowDrop", typeof(DragDrop), inherits: true); /// /// Gets a value indicating whether the given element can be used as the target of a drag-and-drop operation. diff --git a/src/Avalonia.Styling/Styling/ChildSelector.cs b/src/Avalonia.Styling/Styling/ChildSelector.cs index bcbf358721..8b1d2a8553 100644 --- a/src/Avalonia.Styling/Styling/ChildSelector.cs +++ b/src/Avalonia.Styling/Styling/ChildSelector.cs @@ -24,6 +24,9 @@ namespace Avalonia.Styling /// public override bool InTemplate => _parent.InTemplate; + /// + public override bool IsCombinator => true; + /// public override Type TargetType => null; @@ -43,11 +46,24 @@ namespace Avalonia.Styling if (controlParent != null) { - return _parent.Match((IStyleable)controlParent, subscribe); + var parentMatch = _parent.Match((IStyleable)controlParent, subscribe); + + if (parentMatch.Result == SelectorMatchResult.Sometimes) + { + return parentMatch; + } + else if (parentMatch.IsMatch) + { + return SelectorMatch.AlwaysThisInstance; + } + else + { + return SelectorMatch.NeverThisInstance; + } } else { - return SelectorMatch.False; + return SelectorMatch.NeverThisInstance; } } diff --git a/src/Avalonia.Styling/Styling/DescendentSelector.cs b/src/Avalonia.Styling/Styling/DescendentSelector.cs index 943b3f0161..a81908f23d 100644 --- a/src/Avalonia.Styling/Styling/DescendentSelector.cs +++ b/src/Avalonia.Styling/Styling/DescendentSelector.cs @@ -22,6 +22,9 @@ namespace Avalonia.Styling _parent = parent; } + /// + public override bool IsCombinator => true; + /// public override bool InTemplate => _parent.InTemplate; @@ -51,16 +54,13 @@ namespace Avalonia.Styling { var match = _parent.Match((IStyleable)c, subscribe); - if (match.ImmediateResult != null) + if (match.Result == SelectorMatchResult.Sometimes) { - if (match.ImmediateResult == true) - { - return SelectorMatch.True; - } + descendantMatches.Add(match.Activator); } - else + else if (match.IsMatch) { - descendantMatches.Add(match.ObservableResult); + return SelectorMatch.AlwaysThisInstance; } } } @@ -71,7 +71,7 @@ namespace Avalonia.Styling } else { - return SelectorMatch.False; + return SelectorMatch.NeverThisInstance; } } diff --git a/src/Avalonia.Styling/Styling/IStyle.cs b/src/Avalonia.Styling/Styling/IStyle.cs index 3cf8491ae3..da2a08f04d 100644 --- a/src/Avalonia.Styling/Styling/IStyle.cs +++ b/src/Avalonia.Styling/Styling/IStyle.cs @@ -17,7 +17,12 @@ namespace Avalonia.Styling /// /// The control that contains this style. May be null. /// - void Attach(IStyleable control, IStyleHost container); + /// + /// True if the style can match a control of type + /// (even if it does not match this control specifically); false if the style + /// can never match. + /// + bool Attach(IStyleable control, IStyleHost container); void Detach(); } diff --git a/src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs b/src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs index 541e8646b1..cfc0998fe0 100644 --- a/src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs +++ b/src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs @@ -30,6 +30,9 @@ namespace Avalonia.Styling /// public override bool InTemplate => _previous?.InTemplate ?? false; + /// + public override bool IsCombinator => false; + /// /// Gets the name of the control to match. /// @@ -78,7 +81,8 @@ namespace Avalonia.Styling } else { - return new SelectorMatch((control.GetValue(_property) ?? string.Empty).Equals(_value)); + var result = (control.GetValue(_property) ?? string.Empty).Equals(_value); + return result ? SelectorMatch.AlwaysThisInstance : SelectorMatch.NeverThisInstance; } } diff --git a/src/Avalonia.Styling/Styling/Selector.cs b/src/Avalonia.Styling/Styling/Selector.cs index 24c6be9b7a..af209ea970 100644 --- a/src/Avalonia.Styling/Styling/Selector.cs +++ b/src/Avalonia.Styling/Styling/Selector.cs @@ -17,6 +17,14 @@ namespace Avalonia.Styling /// public abstract bool InTemplate { get; } + /// + /// Gets a value indicating whether this selector is a combinator. + /// + /// + /// A combinator is a selector such as Child or Descendent which links simple selectors. + /// + public abstract bool IsCombinator { get; } + /// /// Gets the target type of the selector, if available. /// @@ -33,25 +41,32 @@ namespace Avalonia.Styling /// A . public SelectorMatch Match(IStyleable control, bool subscribe = true) { - List> inputs = new List>(); - Selector selector = this; + var inputs = new List>(); + var selector = this; + var alwaysThisType = true; + var hitCombinator = false; while (selector != null) { - if (selector.InTemplate && control.TemplatedParent == null) - { - return SelectorMatch.False; - } + hitCombinator |= selector.IsCombinator; var match = selector.Evaluate(control, subscribe); - if (match.ImmediateResult == false) + if (!match.IsMatch) + { + return hitCombinator ? SelectorMatch.NeverThisInstance : match; + } + else if (selector.InTemplate && control.TemplatedParent == null) + { + return SelectorMatch.NeverThisInstance; + } + else if (match.Result == SelectorMatchResult.AlwaysThisInstance) { - return match; + alwaysThisType = false; } - else if (match.ObservableResult != null) + else if (match.Result == SelectorMatchResult.Sometimes) { - inputs.Add(match.ObservableResult); + inputs.Add(match.Activator); } selector = selector.MovePrevious(); @@ -63,7 +78,9 @@ namespace Avalonia.Styling } else { - return SelectorMatch.True; + return alwaysThisType && !hitCombinator ? + SelectorMatch.AlwaysThisType : + SelectorMatch.AlwaysThisInstance; } } diff --git a/src/Avalonia.Styling/Styling/SelectorMatch.cs b/src/Avalonia.Styling/Styling/SelectorMatch.cs index f325578389..63b89e9e97 100644 --- a/src/Avalonia.Styling/Styling/SelectorMatch.cs +++ b/src/Avalonia.Styling/Styling/SelectorMatch.cs @@ -5,51 +5,95 @@ using System; namespace Avalonia.Styling { + /// + /// Describes how a matches a control and its type. + /// + public enum SelectorMatchResult + { + /// + /// The selector never matches this type. + /// + NeverThisType, + + /// + /// The selector never matches this instance, but can match this type. + /// + NeverThisInstance, + + /// + /// The selector always matches this type. + /// + AlwaysThisType, + + /// + /// The selector always matches this instance, but doesn't always match this type. + /// + AlwaysThisInstance, + + /// + /// The selector matches this instance based on the . + /// + Sometimes, + } + /// /// Holds the result of a match. /// /// - /// There are two types of selectors - ones whose match can never change for a particular - /// control (such as ) and ones whose result can - /// change over time (such as . For the first - /// category of selectors, the value of will be set but for the - /// second, will be null and will - /// hold an observable which tracks the match. + /// A selector match describes whether and how a matches a control, and + /// in addition whether the selector can ever match a control of the same type. /// public class SelectorMatch { - public static readonly SelectorMatch False = new SelectorMatch(false); + /// + /// A selector match with the result of . + /// + public static readonly SelectorMatch NeverThisType = new SelectorMatch(SelectorMatchResult.NeverThisType); - public static readonly SelectorMatch True = new SelectorMatch(true); + /// + /// A selector match with the result of . + /// + public static readonly SelectorMatch NeverThisInstance = new SelectorMatch(SelectorMatchResult.NeverThisInstance); /// - /// Initializes a new instance of the class. + /// A selector match with the result of . /// - /// The immediate match value. - public SelectorMatch(bool match) - { - ImmediateResult = match; - } + public static readonly SelectorMatch AlwaysThisType = new SelectorMatch(SelectorMatchResult.AlwaysThisType); /// - /// Initializes a new instance of the class. + /// Gets a selector match with the result of . /// - /// The observable match value. + public static readonly SelectorMatch AlwaysThisInstance = new SelectorMatch(SelectorMatchResult.AlwaysThisInstance); + + /// + /// Initializes a new instance of the class with a + /// result. + /// + /// The match activator. public SelectorMatch(IObservable match) { - ObservableResult = match; + Contract.Requires(match != null); + + Result = SelectorMatchResult.Sometimes; + Activator = match; } + private SelectorMatch(SelectorMatchResult result) => Result = result; + /// - /// Gets the immediate result of the selector match, in the case of selectors that cannot - /// change over time. + /// Gets a value indicating whether the match was positive. + /// + public bool IsMatch => Result >= SelectorMatchResult.AlwaysThisType; + + /// + /// Gets the result of the match. /// - public bool? ImmediateResult { get; } + public SelectorMatchResult Result { get; } /// /// Gets an observable which tracks the selector match, in the case of selectors that can /// change over time. /// - public IObservable ObservableResult { get; } + public IObservable Activator { get; } } } diff --git a/src/Avalonia.Styling/Styling/Setter.cs b/src/Avalonia.Styling/Styling/Setter.cs index 54d0a7d40f..cfe17a4291 100644 --- a/src/Avalonia.Styling/Styling/Setter.cs +++ b/src/Avalonia.Styling/Styling/Setter.cs @@ -69,7 +69,7 @@ namespace Avalonia.Styling { throw new ArgumentException( "Cannot assign a control to Setter.Value. Wrap the control in a