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/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/parameters.cake b/parameters.cake
deleted file mode 100644
index 4ef7e8e05a..0000000000
--- a/parameters.cake
+++ /dev/null
@@ -1,129 +0,0 @@
-using System.Xml.Linq;
-using System.Linq;
-
-public class Parameters
-{
- public string Configuration { get; private set; }
- public bool SkipTests { get; private set; }
- public string MainRepo { get; private set; }
- public string MasterBranch { get; private set; }
- public string ReleasePlatform { get; private set; }
- public string ReleaseConfiguration { get; private set; }
- public string ReleaseBranchPrefix { get; private set; }
- public string MSBuildSolution { get; private set; }
- public bool IsLocalBuild { get; private set; }
- public bool IsRunningOnUnix { get; private set; }
- public bool IsRunningOnWindows { get; private set; }
- public bool IsRunningOnAppVeyor { get; private set; }
- public bool IsRunningOnAzure { get; private set; }
- public bool IsPullRequest { get; private set; }
- public bool IsMainRepo { get; private set; }
- public bool IsMasterBranch { get; private set; }
- public bool IsReleaseBranch { get; private set; }
- public bool IsTagged { get; private set; }
- public bool IsReleasable { get; private set; }
- public bool IsMyGetRelease { get; private set; }
- public bool IsNuGetRelease { get; private set; }
- public bool PublishTestResults { get; private set; }
- public string Version { get; private set; }
- public DirectoryPath ArtifactsDir { get; private set; }
- public DirectoryPath NugetRoot { get; private set; }
- public DirectoryPath ZipRoot { get; private set; }
- public DirectoryPath BinRoot { get; private set; }
- public DirectoryPath TestResultsRoot { get; private set; }
- public string DirSuffix { get; private set; }
- public DirectoryPathCollection BuildDirs { get; private set; }
- public string FileZipSuffix { get; private set; }
- public FilePath ZipCoreArtifacts { get; private set; }
- public FilePath ZipNuGetArtifacts { get; private set; }
- public DirectoryPath ZipSourceControlCatalogDesktopDirs { get; private set; }
- public FilePath ZipTargetControlCatalogDesktopDirs { get; private set; }
-
- public Parameters(ICakeContext context)
- {
- var buildSystem = context.BuildSystem();
-
- // ARGUMENTS
- Configuration = context.Argument("configuration", "Release");
- SkipTests = context.HasArgument("skip-tests");
-
- // CONFIGURATION
- MainRepo = "https://github.com/AvaloniaUI/Avalonia";
- MasterBranch = "master";
- ReleaseBranchPrefix = "refs/heads/release/";
- ReleaseConfiguration = "Release";
- MSBuildSolution = "./dirs.proj";
-
- // PARAMETERS
- IsLocalBuild = buildSystem.IsLocalBuild;
- IsRunningOnUnix = context.IsRunningOnUnix();
- IsRunningOnWindows = context.IsRunningOnWindows();
- IsRunningOnAppVeyor = buildSystem.AppVeyor.IsRunningOnAppVeyor;
- IsRunningOnAzure = buildSystem.IsRunningOnVSTS || buildSystem.IsRunningOnTFS || context.EnvironmentVariable("LOGNAME") == "vsts";
-
- IsPullRequest = buildSystem.AppVeyor.Environment.PullRequest.IsPullRequest;
- IsMainRepo = StringComparer.OrdinalIgnoreCase.Equals(MainRepo, context.EnvironmentVariable("BUILD_REPOSITORY_URI"));
- IsMasterBranch = StringComparer.OrdinalIgnoreCase.Equals(MasterBranch, context.EnvironmentVariable("BUILD_SOURCEBRANCHNAME"));
- IsReleaseBranch = (context.EnvironmentVariable("BUILD_SOURCEBRANCH")??"").StartsWith(ReleaseBranchPrefix, StringComparison.OrdinalIgnoreCase);
- IsTagged = buildSystem.AppVeyor.Environment.Repository.Tag.IsTag
- && !string.IsNullOrWhiteSpace(buildSystem.AppVeyor.Environment.Repository.Tag.Name);
- IsReleasable = StringComparer.OrdinalIgnoreCase.Equals(ReleaseConfiguration, Configuration);
- IsMyGetRelease = !IsTagged && IsReleasable;
- IsNuGetRelease = IsMainRepo && IsReleasable && IsReleaseBranch;
-
- // VERSION
- Version = context.Argument("force-nuget-version", GetVersion());
-
- if (IsRunningOnAppVeyor)
- {
- string tagVersion = null;
- if (IsTagged)
- {
- 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
- {
- // Use AssemblyVersion with Build as version
- Version += "-build" + context.EnvironmentVariable("APPVEYOR_BUILD_NUMBER") + "-beta";
- }
- }
- else if (IsRunningOnAzure)
- {
- if(!IsNuGetRelease)
- {
- // Use AssemblyVersion with Build as version
- Version += "-build" + context.EnvironmentVariable("BUILD_BUILDID") + "-beta";
- }
-
- PublishTestResults = true;
- }
-
- // DIRECTORIES
- ArtifactsDir = (DirectoryPath)context.Directory("./artifacts");
- NugetRoot = ArtifactsDir.Combine("nuget");
- ZipRoot = ArtifactsDir.Combine("zip");
- BinRoot = ArtifactsDir.Combine("bin");
- TestResultsRoot = ArtifactsDir.Combine("test-results");
- BuildDirs = context.GetDirectories("**/bin") + context.GetDirectories("**/obj");
- DirSuffix = Configuration;
- FileZipSuffix = Version + ".zip";
- ZipCoreArtifacts = ZipRoot.CombineWithFilePath("Avalonia-" + FileZipSuffix);
- ZipNuGetArtifacts = ZipRoot.CombineWithFilePath("Avalonia-NuGet-" + FileZipSuffix);
- ZipSourceControlCatalogDesktopDirs = (DirectoryPath)context.Directory("./samples/ControlCatalog.Desktop/bin/" + DirSuffix + "/net461");
- ZipTargetControlCatalogDesktopDirs = ZipRoot.CombineWithFilePath("ControlCatalog.Desktop-" + FileZipSuffix);
- }
-
- private static string GetVersion()
- {
- var xdoc = XDocument.Load("./build/SharedVersion.props");
- return xdoc.Descendants().First(x => x.Name.LocalName == "Version").Value;
- }
-}
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.Base/Threading/ThreadSafeObjectPool.cs b/src/Avalonia.Base/Threading/ThreadSafeObjectPool.cs
new file mode 100644
index 0000000000..c6845485dc
--- /dev/null
+++ b/src/Avalonia.Base/Threading/ThreadSafeObjectPool.cs
@@ -0,0 +1,29 @@
+using System.Collections.Generic;
+
+namespace Avalonia.Threading
+{
+ public class ThreadSafeObjectPool where T : class, new()
+ {
+ private Stack _stack = new Stack();
+ private object _lock = new object();
+ public static ThreadSafeObjectPool Default { get; } = new ThreadSafeObjectPool();
+
+ public T Get()
+ {
+ lock (_lock)
+ {
+ if(_stack.Count == 0)
+ return new T();
+ return _stack.Pop();
+ }
+ }
+
+ public void Return(T obj)
+ {
+ lock (_stack)
+ {
+ _stack.Push(obj);
+ }
+ }
+ }
+}
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/Presenters/ItemVirtualizerSimple.cs b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
index b177dc50cf..f72a65ead2 100644
--- a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
+++ b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
@@ -517,7 +517,7 @@ namespace Avalonia.Controls.Presenters
if (index >= 0 && index < ItemCount)
{
- if (index < FirstIndex)
+ if (index <= FirstIndex)
{
newOffset = index;
}
@@ -525,10 +525,6 @@ namespace Avalonia.Controls.Presenters
{
newOffset = index - Math.Ceiling(ViewportValue - 1);
}
- else if (OffsetValue + ViewportValue >= ItemCount)
- {
- newOffset = OffsetValue - 1;
- }
if (newOffset != -1)
{
@@ -546,6 +542,11 @@ namespace Avalonia.Controls.Presenters
{
layoutManager.ExecuteLayoutPass();
+ if (newOffset != -1 && newOffset != OffsetValue)
+ {
+ OffsetValue = newOffset;
+ }
+
if (panel.ScrollDirection == Orientation.Vertical)
{
if (container.Bounds.Y < panel.Bounds.Y || container.Bounds.Bottom > panel.Bounds.Bottom)
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 25f12ffa57..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.
///
@@ -72,17 +75,14 @@ namespace Avalonia.Styling
///
protected override SelectorMatch Evaluate(IStyleable control, bool subscribe)
{
- if (!AvaloniaPropertyRegistry.Instance.IsRegistered(control, _property))
- {
- return SelectorMatch.False;
- }
- else if (subscribe)
+ if (subscribe)
{
return new SelectorMatch(control.GetObservable(_property).Select(v => Equals(v ?? string.Empty, _value)));
}
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 .",
- "value");
+ nameof(value));
}
_value = value;
diff --git a/src/Avalonia.Styling/Styling/Style.cs b/src/Avalonia.Styling/Styling/Style.cs
index 23c318a809..27fad58346 100644
--- a/src/Avalonia.Styling/Styling/Style.cs
+++ b/src/Avalonia.Styling/Styling/Style.cs
@@ -106,20 +106,14 @@ namespace Avalonia.Styling
///
bool IResourceProvider.HasResources => _resources?.Count > 0;
- ///
- /// Attaches the style to a control if the style's selector matches.
- ///
- /// The control to attach to.
- ///
- /// The control that contains this style. May be null.
- ///
- public void Attach(IStyleable control, IStyleHost container)
+ ///
+ public bool Attach(IStyleable control, IStyleHost container)
{
if (Selector != null)
{
var match = Selector.Match(control);
- if (match.ImmediateResult != false)
+ if (match.IsMatch)
{
var controlSubscriptions = GetSubscriptions(control);
@@ -129,9 +123,10 @@ namespace Avalonia.Styling
{
foreach (var animation in Animations)
{
- IObservable obsMatch = match.ObservableResult;
+ var obsMatch = match.Activator;
- if (match.ImmediateResult == true)
+ if (match.Result == SelectorMatchResult.AlwaysThisType ||
+ match.Result == SelectorMatchResult.AlwaysThisInstance)
{
obsMatch = Observable.Return(true);
}
@@ -143,13 +138,15 @@ namespace Avalonia.Styling
foreach (var setter in Setters)
{
- var sub = setter.Apply(this, control, match.ObservableResult);
+ var sub = setter.Apply(this, control, match.Activator);
subs.Add(sub);
}
controlSubscriptions.Add(subs);
Subscriptions.Add(subs);
}
+
+ return match.Result != SelectorMatchResult.NeverThisType;
}
else if (control == container)
{
@@ -165,7 +162,10 @@ namespace Avalonia.Styling
controlSubscriptions.Add(subs);
Subscriptions.Add(subs);
+ return true;
}
+
+ return false;
}
///
diff --git a/src/Avalonia.Styling/Styling/Styles.cs b/src/Avalonia.Styling/Styling/Styles.cs
index 22ed8bb241..51499b737a 100644
--- a/src/Avalonia.Styling/Styling/Styles.cs
+++ b/src/Avalonia.Styling/Styling/Styles.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
+using System.Collections.Generic;
using System.Linq;
using Avalonia.Collections;
using Avalonia.Controls;
@@ -15,6 +16,7 @@ namespace Avalonia.Styling
{
private IResourceNode _parent;
private IResourceDictionary _resources;
+ private Dictionary> _cache;
public Styles()
{
@@ -34,6 +36,7 @@ namespace Avalonia.Styling
}
x.ResourcesChanged += SubResourceChanged;
+ _cache = null;
},
x =>
{
@@ -49,6 +52,7 @@ namespace Avalonia.Styling
}
x.ResourcesChanged -= SubResourceChanged;
+ _cache = null;
},
() => { });
}
@@ -97,11 +101,46 @@ namespace Avalonia.Styling
///
/// The control that contains this style. May be null.
///
- public void Attach(IStyleable control, IStyleHost container)
+ public bool Attach(IStyleable control, IStyleHost container)
{
- foreach (IStyle style in this)
+ if (_cache == null)
+ {
+ _cache = new Dictionary>();
+ }
+
+ if (_cache.TryGetValue(control.StyleKey, out var cached))
+ {
+ if (cached != null)
+ {
+ foreach (var style in cached)
+ {
+ style.Attach(control, container);
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+ else
{
- style.Attach(control, container);
+ List result = null;
+
+ foreach (var style in this)
+ {
+ if (style.Attach(control, container))
+ {
+ if (result == null)
+ {
+ result = new List();
+ }
+
+ result.Add(style);
+ }
+ }
+
+ _cache.Add(control.StyleKey, result);
+ return result != null;
}
}
diff --git a/src/Avalonia.Styling/Styling/TemplateSelector.cs b/src/Avalonia.Styling/Styling/TemplateSelector.cs
index 6919b4ad27..7530339883 100644
--- a/src/Avalonia.Styling/Styling/TemplateSelector.cs
+++ b/src/Avalonia.Styling/Styling/TemplateSelector.cs
@@ -23,6 +23,9 @@ namespace Avalonia.Styling
///
public override bool InTemplate => true;
+ ///
+ public override bool IsCombinator => true;
+
///
public override Type TargetType => null;
@@ -46,7 +49,7 @@ namespace Avalonia.Styling
"Cannot call Template selector on control with null TemplatedParent.");
}
- return _parent.Match(templatedParent, subscribe) ?? SelectorMatch.True;
+ return _parent.Match(templatedParent, subscribe);
}
protected override Selector MovePrevious() => null;
diff --git a/src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs b/src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs
index 94c0b75c6e..cd019e6535 100644
--- a/src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs
+++ b/src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs
@@ -68,6 +68,9 @@ namespace Avalonia.Styling
///
public override Type TargetType => _targetType ?? _previous?.TargetType;
+ ///
+ public override bool IsCombinator => false;
+
///
/// Whether the selector matches the concrete or any object which
/// implements .
@@ -101,21 +104,23 @@ namespace Avalonia.Styling
{
if (controlType != TargetType)
{
- return SelectorMatch.False;
+ return SelectorMatch.NeverThisType;
}
}
else
{
if (!TargetType.GetTypeInfo().IsAssignableFrom(controlType.GetTypeInfo()))
{
- return SelectorMatch.False;
+ return SelectorMatch.NeverThisType;
}
}
}
- if (Name != null && control.Name != Name)
+ if (Name != null)
{
- return SelectorMatch.False;
+ return control.Name == Name ?
+ SelectorMatch.AlwaysThisInstance :
+ SelectorMatch.NeverThisInstance;
}
if (_classes.IsValueCreated && _classes.Value.Count > 0)
@@ -127,12 +132,14 @@ namespace Avalonia.Styling
}
else
{
- return new SelectorMatch(Matches(control.Classes));
+ return Matches(control.Classes) ?
+ SelectorMatch.AlwaysThisInstance :
+ SelectorMatch.NeverThisInstance;
}
}
else
{
- return SelectorMatch.True;
+ return SelectorMatch.AlwaysThisType;
}
}
diff --git a/src/Avalonia.Visuals/Matrix.cs b/src/Avalonia.Visuals/Matrix.cs
index cdaaa2b4b4..d083a2aaf8 100644
--- a/src/Avalonia.Visuals/Matrix.cs
+++ b/src/Avalonia.Visuals/Matrix.cs
@@ -293,13 +293,13 @@ namespace Avalonia
/// The inverted matrix.
public Matrix Invert()
{
- if (GetDeterminant() == 0)
+ double d = GetDeterminant();
+
+ if (d == 0)
{
throw new InvalidOperationException("Transform is not invertible.");
}
- double d = GetDeterminant();
-
return new Matrix(
_m22 / d,
-_m12 / d,
diff --git a/src/Avalonia.Visuals/Media/DrawingContext.cs b/src/Avalonia.Visuals/Media/DrawingContext.cs
index 6b19167381..e7d6df5a93 100644
--- a/src/Avalonia.Visuals/Media/DrawingContext.cs
+++ b/src/Avalonia.Visuals/Media/DrawingContext.cs
@@ -2,23 +2,26 @@ using System;
using System.Collections.Generic;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
+using Avalonia.Threading;
using Avalonia.Visuals.Media.Imaging;
namespace Avalonia.Media
{
public sealed class DrawingContext : IDisposable
{
+ private readonly bool _ownsImpl;
private int _currentLevel;
- static readonly Stack> StateStackPool = new Stack>();
- static readonly Stack> TransformStackPool = new Stack>();
+ private static ThreadSafeObjectPool> StateStackPool { get; } =
+ ThreadSafeObjectPool>.Default;
- private Stack _states = StateStackPool.Count == 0 ? new Stack() : StateStackPool.Pop();
+ private static ThreadSafeObjectPool> TransformStackPool { get; } =
+ ThreadSafeObjectPool>.Default;
- private Stack _transformContainers = TransformStackPool.Count == 0
- ? new Stack()
- : TransformStackPool.Pop();
+ private Stack _states = StateStackPool.Get();
+
+ private Stack _transformContainers = TransformStackPool.Get();
readonly struct TransformContainer
{
@@ -35,6 +38,13 @@ namespace Avalonia.Media
public DrawingContext(IDrawingContextImpl impl)
{
PlatformImpl = impl;
+ _ownsImpl = true;
+ }
+
+ public DrawingContext(IDrawingContextImpl impl, bool ownsImpl)
+ {
+ _ownsImpl = ownsImpl;
+ PlatformImpl = impl;
}
public IDrawingContextImpl PlatformImpl { get; }
@@ -299,11 +309,14 @@ namespace Avalonia.Media
{
while (_states.Count != 0)
_states.Peek().Dispose();
- StateStackPool.Push(_states);
+ StateStackPool.Return(_states);
_states = null;
- TransformStackPool.Push(_transformContainers);
+ if (_transformContainers.Count != 0)
+ throw new InvalidOperationException("Transform container stack is non-empty");
+ TransformStackPool.Return(_transformContainers);
_transformContainers = null;
- PlatformImpl.Dispose();
+ if (_ownsImpl)
+ PlatformImpl.Dispose();
}
private static bool PenIsVisible(Pen pen)
diff --git a/src/Avalonia.Visuals/Media/GradientStop.cs b/src/Avalonia.Visuals/Media/GradientStop.cs
index 00d96a0b3c..25a6eb10dc 100644
--- a/src/Avalonia.Visuals/Media/GradientStop.cs
+++ b/src/Avalonia.Visuals/Media/GradientStop.cs
@@ -11,13 +11,13 @@ namespace Avalonia.Media
///
/// Describes the property.
///
- public static StyledProperty OffsetProperty =
+ public static readonly StyledProperty OffsetProperty =
AvaloniaProperty.Register(nameof(Offset));
///
/// Describes the property.
///
- public static StyledProperty ColorProperty =
+ public static readonly StyledProperty ColorProperty =
AvaloniaProperty.Register(nameof(Color));
///
diff --git a/src/Avalonia.Visuals/Media/Typeface.cs b/src/Avalonia.Visuals/Media/Typeface.cs
index 6dde2bb591..37ac0953bf 100644
--- a/src/Avalonia.Visuals/Media/Typeface.cs
+++ b/src/Avalonia.Visuals/Media/Typeface.cs
@@ -7,7 +7,7 @@ namespace Avalonia.Media
///
public class Typeface
{
- public static Typeface Default = new Typeface(FontFamily.Default);
+ public static readonly Typeface Default = new Typeface(FontFamily.Default);
///
/// Initializes a new instance of the class.
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
index 8000d413ea..159c3cd0fa 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
@@ -220,6 +220,7 @@ namespace Avalonia.Rendering.SceneGraph
Transform = Transform,
ClipBounds = ClipBounds,
ClipToBounds = ClipToBounds,
+ LayoutBounds = LayoutBounds,
GeometryClip = GeometryClip,
_opacity = Opacity,
OpacityMask = OpacityMask,
diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
index 0b5e25fa9f..38d207a31d 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
+++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
@@ -11,6 +11,7 @@
+
diff --git a/src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs b/src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs
index fa9d364fc0..e09d9bfd17 100644
--- a/src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/AvaloniaTypeConverters.cs
@@ -51,6 +51,14 @@ namespace Avalonia.Markup.Xaml
/// The type converter.
public static Type GetTypeConverter(Type type)
{
+ if (type.IsConstructedGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
+ {
+ var inner = GetTypeConverter(type.GetGenericArguments()[0]);
+ if (inner == null)
+ return null;
+ return typeof(NullableTypeConverter<>).MakeGenericType(inner);
+ }
+
if (_converters.TryGetValue(type, out var result))
{
return result;
diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/NullableTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/NullableTypeConverter.cs
new file mode 100644
index 0000000000..5e7a31da56
--- /dev/null
+++ b/src/Markup/Avalonia.Markup.Xaml/Converters/NullableTypeConverter.cs
@@ -0,0 +1,89 @@
+using System;
+using System.Collections;
+using System.ComponentModel;
+using System.Globalization;
+
+namespace Avalonia.Markup.Xaml.Converters
+{
+ public class NullableTypeConverter : TypeConverter where T : TypeConverter, new()
+ {
+ private TypeConverter _inner;
+
+ public NullableTypeConverter()
+ {
+ _inner = new T();
+ }
+
+ public NullableTypeConverter(TypeConverter inner)
+ {
+ _inner = inner;
+ }
+
+
+ public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
+ {
+ if (value == null)
+ return null;
+ return _inner.ConvertTo(context, culture, value, destinationType);
+ }
+
+ public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
+ {
+ if (value == null)
+ return null;
+ if (value as string == "")
+ return null;
+ return _inner.ConvertFrom(context, culture, value);
+ }
+
+ public override object CreateInstance(ITypeDescriptorContext context, IDictionary propertyValues)
+ {
+ return _inner.CreateInstance(context, propertyValues);
+ }
+
+ public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
+ {
+ return _inner.GetStandardValuesSupported(context);
+ }
+
+ public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
+ {
+ return _inner.GetStandardValuesExclusive(context);
+ }
+
+ public override bool GetCreateInstanceSupported(ITypeDescriptorContext context)
+ {
+ return _inner.GetCreateInstanceSupported(context);
+ }
+
+ public override bool GetPropertiesSupported(ITypeDescriptorContext context)
+ {
+ return _inner.GetPropertiesSupported(context);
+ }
+
+ public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
+ {
+ return _inner.GetStandardValues(context);
+ }
+
+ public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
+ {
+ return _inner.GetProperties(context, value, attributes);
+ }
+
+ public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
+ {
+ return _inner.CanConvertTo(context, destinationType);
+ }
+
+ public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
+ {
+ return _inner.CanConvertFrom(context, sourceType);
+ }
+
+ public override bool IsValid(ITypeDescriptorContext context, object value)
+ {
+ return _inner.IsValid(context, value);
+ }
+ }
+}
diff --git a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs
index fc2656f236..9273011cb9 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs
@@ -62,12 +62,14 @@ namespace Avalonia.Markup.Xaml.Styling
IResourceNode IResourceNode.ResourceParent => _parent;
///
- public void Attach(IStyleable control, IStyleHost container)
+ public bool Attach(IStyleable control, IStyleHost container)
{
if (Source != null)
{
- Loaded.Attach(control, container);
+ return Loaded.Attach(control, container);
}
+
+ return false;
}
public void Detach()
diff --git a/src/Skia/Avalonia.Skia/GlRenderTarget.cs b/src/Skia/Avalonia.Skia/GlRenderTarget.cs
index 3360255dae..81c70e2be7 100644
--- a/src/Skia/Avalonia.Skia/GlRenderTarget.cs
+++ b/src/Skia/Avalonia.Skia/GlRenderTarget.cs
@@ -35,6 +35,7 @@ namespace Avalonia.Skia
gl.ClearStencil(0);
gl.ClearColor(0, 0, 0, 0);
gl.Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ _grContext.ResetContext();
GRBackendRenderTarget renderTarget =
new GRBackendRenderTarget(size.Width, size.Height, disp.SampleCount, disp.StencilSize,
diff --git a/src/Skia/Avalonia.Skia/SKTypefaceCollection.cs b/src/Skia/Avalonia.Skia/SKTypefaceCollection.cs
index 687fe7feca..17c51dbb6e 100644
--- a/src/Skia/Avalonia.Skia/SKTypefaceCollection.cs
+++ b/src/Skia/Avalonia.Skia/SKTypefaceCollection.cs
@@ -1,72 +1,115 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+
using Avalonia.Media;
+
using SkiaSharp;
namespace Avalonia.Skia
{
internal class SKTypefaceCollection
{
- private readonly ConcurrentDictionary _cachedTypefaces =
- new ConcurrentDictionary();
+ private readonly ConcurrentDictionary> _fontFamilies =
+ new ConcurrentDictionary>();
public void AddTypeFace(SKTypeface typeface)
{
- var key = new FontKey(typeface.FamilyName, (SKFontStyleWeight)typeface.FontWeight, typeface.FontSlant);
+ var key = new FontKey((SKFontStyleWeight)typeface.FontWeight, typeface.FontSlant);
- _cachedTypefaces.TryAdd(key, typeface);
+ if (!_fontFamilies.TryGetValue(typeface.FamilyName, out var fontFamily))
+ {
+ fontFamily = new ConcurrentDictionary();
+
+ _fontFamilies.TryAdd(typeface.FamilyName, fontFamily);
+ }
+
+ fontFamily.TryAdd(key, typeface);
}
public SKTypeface GetTypeFace(Typeface typeface)
{
- SKFontStyleSlant skStyle = SKFontStyleSlant.Upright;
+ var styleSlant = SKFontStyleSlant.Upright;
switch (typeface.Style)
{
case FontStyle.Italic:
- skStyle = SKFontStyleSlant.Italic;
+ styleSlant = SKFontStyleSlant.Italic;
break;
case FontStyle.Oblique:
- skStyle = SKFontStyleSlant.Oblique;
+ styleSlant = SKFontStyleSlant.Oblique;
break;
}
- var key = new FontKey(typeface.FontFamily.Name, (SKFontStyleWeight)typeface.Weight, skStyle);
+ if (!_fontFamilies.TryGetValue(typeface.FontFamily.Name, out var fontFamily))
+ {
+ return TypefaceCache.Default;
+ }
+
+ var weight = (SKFontStyleWeight)typeface.Weight;
+
+ var key = new FontKey(weight, styleSlant);
+
+ return fontFamily.GetOrAdd(key, GetFallback(fontFamily, key));
+ }
+
+ private static SKTypeface GetFallback(IDictionary fontFamily, FontKey key)
+ {
+ var keys = fontFamily.Keys.Where(
+ x => ((int)x.Weight <= (int)key.Weight || (int)x.Weight > (int)key.Weight) && x.Slant == key.Slant).ToArray();
+
+ if (!keys.Any())
+ {
+ keys = fontFamily.Keys.Where(
+ x => x.Weight == key.Weight && (x.Slant >= key.Slant || x.Slant < key.Slant)).ToArray();
+
+ if (!keys.Any())
+ {
+ keys = fontFamily.Keys.Where(
+ x => ((int)x.Weight <= (int)key.Weight || (int)x.Weight > (int)key.Weight) &&
+ (x.Slant >= key.Slant || x.Slant < key.Slant)).ToArray();
+ }
+ }
+
+ key = keys.FirstOrDefault();
+
+ fontFamily.TryGetValue(key, out var typeface);
- return _cachedTypefaces.TryGetValue(key, out var skTypeface) ? skTypeface : TypefaceCache.Default;
+ return typeface;
}
private struct FontKey
{
- public readonly string Name;
public readonly SKFontStyleSlant Slant;
public readonly SKFontStyleWeight Weight;
- public FontKey(string name, SKFontStyleWeight weight, SKFontStyleSlant slant)
+ public FontKey(SKFontStyleWeight weight, SKFontStyleSlant slant)
{
- Name = name;
Slant = slant;
Weight = weight;
}
public override int GetHashCode()
{
- int hash = 17;
- hash = hash * 31 + Name.GetHashCode();
- hash = hash * 31 + (int)Slant;
- hash = hash * 31 + (int)Weight;
+ var hash = 17;
+ hash = (hash * 31) + (int)Slant;
+ hash = (hash * 31) + (int)Weight;
return hash;
}
public override bool Equals(object other)
{
- return other is FontKey ? Equals((FontKey)other) : false;
+ return other is FontKey key && this.Equals(key);
}
private bool Equals(FontKey other)
{
- return Name == other.Name && Slant == other.Slant &&
+ return Slant == other.Slant &&
Weight == other.Weight;
}
}
diff --git a/src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs b/src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs
index b9a8c9b98b..ab8ee85a54 100644
--- a/src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs
+++ b/src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs
@@ -1,3 +1,6 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
using System.Collections.Concurrent;
using Avalonia.Media;
using Avalonia.Media.Fonts;
diff --git a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs
index dcd4b36d56..4b7eae1af4 100644
--- a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs
+++ b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs
@@ -45,6 +45,7 @@ namespace Avalonia.Skia
///
/// Create backing Skia surface.
///
+ /// GPU.
/// Width.
/// Height.
/// Format.
diff --git a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs
index eb3a11b2a1..c9d6fa6c11 100644
--- a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs
+++ b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs
@@ -123,6 +123,7 @@ namespace Avalonia.Skia
///
/// Create framebuffer from given bitmap.
///
+ /// Parent bitmap impl.
/// Bitmap.
public BitmapFramebuffer(WriteableBitmapImpl parent, SKBitmap bitmap)
{
diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs
index 6174bc360f..f4eb0e2ea8 100644
--- a/src/Windows/Avalonia.Win32/WindowImpl.cs
+++ b/src/Windows/Avalonia.Win32/WindowImpl.cs
@@ -153,17 +153,24 @@ namespace Avalonia.Win32
public void Resize(Size value)
{
- if (value != ClientSize)
+ int requestedClientWidth = (int)(value.Width * Scaling);
+ int requestedClientHeight = (int)(value.Height * Scaling);
+ UnmanagedMethods.RECT clientRect;
+ UnmanagedMethods.GetClientRect(_hwnd, out clientRect);
+
+ // do comparison after scaling to avoid rounding issues
+ if (requestedClientWidth != clientRect.Width || requestedClientHeight != clientRect.Height)
{
- value *= Scaling;
-
+ UnmanagedMethods.RECT windowRect;
+ UnmanagedMethods.GetWindowRect(_hwnd, out windowRect);
+
UnmanagedMethods.SetWindowPos(
_hwnd,
IntPtr.Zero,
0,
0,
- (int)value.Width,
- (int)value.Height,
+ requestedClientWidth + (windowRect.Width - clientRect.Width),
+ requestedClientHeight + (windowRect.Height - clientRect.Height),
UnmanagedMethods.SetWindowPosFlags.SWP_RESIZE);
}
}
diff --git a/tests/Avalonia.Animation.UnitTests/Avalonia.Animation.UnitTests.csproj b/tests/Avalonia.Animation.UnitTests/Avalonia.Animation.UnitTests.csproj
index 1b2ba3c7de..ef515ce155 100644
--- a/tests/Avalonia.Animation.UnitTests/Avalonia.Animation.UnitTests.csproj
+++ b/tests/Avalonia.Animation.UnitTests/Avalonia.Animation.UnitTests.csproj
@@ -1,9 +1,11 @@
- netcoreapp2.0
+ netcoreapp2.0;net47
Library
+ true
+
@@ -22,4 +24,4 @@
-
\ No newline at end of file
+
diff --git a/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj b/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj
index 373b3f7196..6e885f33be 100644
--- a/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj
+++ b/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj
@@ -1,9 +1,11 @@
- netcoreapp2.0
+ netcoreapp2.0;net47
Library
+ true
+
diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs
index c90683959e..b56afa33a4 100644
--- a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs
+++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs
@@ -558,11 +558,29 @@ namespace Avalonia.Base.UnitTests.Data.Core
var result = run();
result.Item1.Subscribe(x => { });
- GC.Collect();
+ // Mono trickery
+ GC.Collect(2);
+ GC.WaitForPendingFinalizers();
+ GC.WaitForPendingFinalizers();
+ GC.Collect(2);
+
Assert.Null(result.Item2.Target);
}
+ [Fact]
+ public void Should_Not_Throw_Exception_On_Unsubscribe_When_Already_Unsubscribed()
+ {
+ var source = new Class1 { Foo = "foo" };
+ var target = new PropertyAccessorNode("Foo", false);
+ Assert.NotNull(target);
+ target.Target = new WeakReference(source);
+ target.Subscribe(_ => { });
+ target.Unsubscribe();
+ target.Unsubscribe();
+ Assert.True(true);
+ }
+
private interface INext
{
int PropertyChangedSubscriptionCount { get; }
diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/Plugins/DataAnnotationsValidationPluginTests.cs b/tests/Avalonia.Base.UnitTests/Data/Core/Plugins/DataAnnotationsValidationPluginTests.cs
index 2bffb7b84a..378c225e23 100644
--- a/tests/Avalonia.Base.UnitTests/Data/Core/Plugins/DataAnnotationsValidationPluginTests.cs
+++ b/tests/Avalonia.Base.UnitTests/Data/Core/Plugins/DataAnnotationsValidationPluginTests.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
+using System.Linq;
using Avalonia.Data;
using Avalonia.Data.Core.Plugins;
using Avalonia.UnitTests;
@@ -86,17 +87,18 @@ namespace Avalonia.Markup.UnitTests.Data.Plugins
validator.SetValue("123456", BindingPriority.LocalValue);
validator.SetValue("abcdefghijklm", BindingPriority.LocalValue);
- Assert.Equal(new[]
- {
- new BindingNotification(null),
- new BindingNotification("123456"),
- new BindingNotification(
- new AggregateException(
- new ValidationException("The PhoneNumber field is not a valid phone number."),
- new ValidationException("The field PhoneNumber must be a string or array type with a maximum length of '10'.")),
- BindingErrorType.DataValidationError,
- "abcdefghijklm"),
- }, result);
+ Assert.Equal(3, result.Count);
+ Assert.Equal(new BindingNotification(null), result[0]);
+ Assert.Equal(new BindingNotification("123456"), result[1]);
+ var errorResult = (BindingNotification)result[2];
+ Assert.Equal(BindingErrorType.DataValidationError, errorResult.ErrorType);
+ Assert.Equal("abcdefghijklm", errorResult.Value);
+ var exceptions = ((AggregateException)(errorResult.Error)).InnerExceptions;
+ Assert.True(exceptions.Any(ex =>
+ ex.Message.Contains("The PhoneNumber field is not a valid phone number.")));
+ Assert.True(exceptions.Any(ex =>
+ ex.Message.Contains("The field PhoneNumber must be a string or array type with a maximum length of '10'.")));
+
}
private class Data
diff --git a/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj b/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj
index b418d56fcd..aa470bce9d 100644
--- a/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj
+++ b/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj
@@ -1,10 +1,12 @@
- netcoreapp2.0
+ netcoreapp2.0;net47
latest
Library
+ true
+
@@ -26,4 +28,4 @@
-
\ No newline at end of file
+
diff --git a/tests/Avalonia.Controls.UnitTests/GridLayoutTests.cs b/tests/Avalonia.Controls.UnitTests/GridLayoutTests.cs
index fbb90de505..93163f4a92 100644
--- a/tests/Avalonia.Controls.UnitTests/GridLayoutTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/GridLayoutTests.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System.Collections;
+using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Avalonia.Controls.Utils;
@@ -16,7 +17,7 @@ namespace Avalonia.Controls.UnitTests
[InlineData("100, 200, 300", 600d, 600d, new[] { 100d, 200d, 300d })]
[InlineData("100, 200, 300", 400d, 400d, new[] { 100d, 200d, 100d })]
public void MeasureArrange_AllPixelLength_Correct(string length, double containerLength,
- double expectedDesiredLength, IList expectedLengthList)
+ double expectedDesiredLength, IList expectedLengthList)
{
TestRowDefinitionsOnly(length, containerLength, expectedDesiredLength, expectedLengthList);
}
@@ -25,7 +26,7 @@ namespace Avalonia.Controls.UnitTests
[InlineData("*,2*,3*", 0d, 0d, new[] { 0d, 0d, 0d })]
[InlineData("*,2*,3*", 600d, 0d, new[] { 100d, 200d, 300d })]
public void MeasureArrange_AllStarLength_Correct(string length, double containerLength,
- double expectedDesiredLength, IList expectedLengthList)
+ double expectedDesiredLength, IList expectedLengthList)
{
TestRowDefinitionsOnly(length, containerLength, expectedDesiredLength, expectedLengthList);
}
@@ -36,7 +37,7 @@ namespace Avalonia.Controls.UnitTests
[InlineData("100,2*,3*", 100d, 100d, new[] { 100d, 0d, 0d })]
[InlineData("100,2*,3*", 50d, 50d, new[] { 50d, 0d, 0d })]
public void MeasureArrange_MixStarPixelLength_Correct(string length, double containerLength,
- double expectedDesiredLength, IList expectedLengthList)
+ double expectedDesiredLength, IList expectedLengthList)
{
TestRowDefinitionsOnly(length, containerLength, expectedDesiredLength, expectedLengthList);
}
@@ -49,7 +50,7 @@ namespace Avalonia.Controls.UnitTests
[InlineData("100,200,Auto", 100d, 100d, new[] { 100d, 0d, 0d })]
[InlineData("100,200,Auto", 50d, 50d, new[] { 50d, 0d, 0d })]
public void MeasureArrange_MixAutoPixelLength_Correct(string length, double containerLength,
- double expectedDesiredLength, IList expectedLengthList)
+ double expectedDesiredLength, IList expectedLengthList)
{
TestRowDefinitionsOnly(length, containerLength, expectedDesiredLength, expectedLengthList);
}
@@ -58,7 +59,7 @@ namespace Avalonia.Controls.UnitTests
[InlineData("*,2*,Auto", 0d, 0d, new[] { 0d, 0d, 0d })]
[InlineData("*,2*,Auto", 600d, 0d, new[] { 200d, 400d, 0d })]
public void MeasureArrange_MixAutoStarLength_Correct(string length, double containerLength,
- double expectedDesiredLength, IList expectedLengthList)
+ double expectedDesiredLength, IList expectedLengthList)
{
TestRowDefinitionsOnly(length, containerLength, expectedDesiredLength, expectedLengthList);
}
@@ -69,14 +70,24 @@ namespace Avalonia.Controls.UnitTests
[InlineData("*,200,Auto", 200d, 200d, new[] { 0d, 200d, 0d })]
[InlineData("*,200,Auto", 100d, 100d, new[] { 0d, 100d, 0d })]
public void MeasureArrange_MixAutoStarPixelLength_Correct(string length, double containerLength,
- double expectedDesiredLength, IList expectedLengthList)
+ double expectedDesiredLength, IList expectedLengthList)
{
TestRowDefinitionsOnly(length, containerLength, expectedDesiredLength, expectedLengthList);
}
+
+ ///
+ /// This is needed because Mono somehow converts double array to object array in attribute metadata
+ ///
+ static void AssertEqual(IList expected, IReadOnlyList actual)
+ {
+ var conv = expected.Cast().ToArray();
+ Assert.Equal(conv, actual);
+ }
+
[SuppressMessage("ReSharper", "ParameterOnlyUsedForPreconditionCheck.Local")]
private static void TestRowDefinitionsOnly(string length, double containerLength,
- double expectedDesiredLength, IList expectedLengthList)
+ double expectedDesiredLength, IList expectedLengthList)
{
// Arrange
var layout = new GridLayout(new RowDefinitions(length));
@@ -84,11 +95,11 @@ namespace Avalonia.Controls.UnitTests
// Measure - Action & Assert
var measure = layout.Measure(containerLength);
Assert.Equal(expectedDesiredLength, measure.DesiredLength);
- Assert.Equal(expectedLengthList, measure.LengthList);
+ AssertEqual(expectedLengthList, measure.LengthList);
// Arrange - Action & Assert
var arrange = layout.Arrange(containerLength, measure);
- Assert.Equal(expectedLengthList, arrange.LengthList);
+ AssertEqual(expectedLengthList, arrange.LengthList);
}
[Theory]
@@ -99,7 +110,7 @@ namespace Avalonia.Controls.UnitTests
[InlineData("*,2*,Auto", 0d, new[] { Inf, Inf, 0d }, new[] { 0d, 0d, 0d })]
[InlineData("*,200,Auto", 200d, new[] { Inf, 200d, 0d }, new[] { 0d, 200d, 0d })]
public void MeasureArrange_InfiniteMeasure_Correct(string length, double expectedDesiredLength,
- IList expectedMeasureList, IList expectedArrangeList)
+ IList expectedMeasureList, IList expectedArrangeList)
{
// Arrange
var layout = new GridLayout(new RowDefinitions(length));
@@ -107,34 +118,34 @@ namespace Avalonia.Controls.UnitTests
// Measure - Action & Assert
var measure = layout.Measure(Inf);
Assert.Equal(expectedDesiredLength, measure.DesiredLength);
- Assert.Equal(expectedMeasureList, measure.LengthList);
+ AssertEqual(expectedMeasureList, measure.LengthList);
// Arrange - Action & Assert
var arrange = layout.Arrange(measure.DesiredLength, measure);
- Assert.Equal(expectedArrangeList, arrange.LengthList);
+ AssertEqual(expectedArrangeList, arrange.LengthList);
}
[Theory]
[InlineData("Auto,*,*", new[] { 100d, 100d, 100d }, 600d, 300d, new[] { 100d, 250d, 250d })]
public void MeasureArrange_ChildHasSize_Correct(string length,
- IList childLengthList, double containerLength,
- double expectedDesiredLength, IList expectedLengthList)
+ IList childLengthList, double containerLength,
+ double expectedDesiredLength, IList expectedLengthList)
{
// Arrange
var lengthList = new ColumnDefinitions(length);
var layout = new GridLayout(lengthList);
layout.AppendMeasureConventions(
Enumerable.Range(0, lengthList.Count).ToDictionary(x => x, x => (x, 1)),
- x => childLengthList[x]);
+ x => (double)childLengthList[x]);
// Measure - Action & Assert
var measure = layout.Measure(containerLength);
Assert.Equal(expectedDesiredLength, measure.DesiredLength);
- Assert.Equal(expectedLengthList, measure.LengthList);
+ AssertEqual(expectedLengthList, measure.LengthList);
// Arrange - Action & Assert
var arrange = layout.Arrange(containerLength, measure);
- Assert.Equal(expectedLengthList, arrange.LengthList);
+ AssertEqual(expectedLengthList, arrange.LengthList);
}
[Theory]
@@ -145,7 +156,7 @@ namespace Avalonia.Controls.UnitTests
[InlineData(160d, 160d, new[] { 100d, 20d, 40d }, new[] { 100d, 20d, 40d })]
public void MeasureArrange_ChildHasSizeAndHasMultiSpan_Correct(
double containerLength, double expectedDesiredLength,
- IList expectedMeasureLengthList, IList expectedArrangeLengthList)
+ IList expectedMeasureLengthList, IList expectedArrangeLengthList)
{
var length = "100,*,2*";
var childLengthList = new[] { 150d, 150d, 150d };
@@ -161,13 +172,13 @@ namespace Avalonia.Controls.UnitTests
// Measure - Action & Assert
var measure = layout.Measure(containerLength);
Assert.Equal(expectedDesiredLength, measure.DesiredLength);
- Assert.Equal(expectedMeasureLengthList, measure.LengthList);
+ AssertEqual(expectedMeasureLengthList, measure.LengthList);
// Arrange - Action & Assert
var arrange = layout.Arrange(
double.IsInfinity(containerLength) ? measure.DesiredLength : containerLength,
measure);
- Assert.Equal(expectedArrangeLengthList, arrange.LengthList);
+ AssertEqual(expectedArrangeLengthList, arrange.LengthList);
}
}
}
diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs
index b992453fb0..7187ea16da 100644
--- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs
+++ b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs
@@ -717,6 +717,45 @@ namespace Avalonia.Controls.UnitTests.Presenters
Assert.Equal(10, target.Panel.Children.Count);
}
+ [Fact]
+ public void Scroll_To_Last_Should_Work()
+ {
+ var target = CreateTarget(itemCount: 11);
+ var scroller = (TestScroller)target.Parent;
+
+ scroller.Width = scroller.Height = 100;
+ scroller.LayoutManager.ExecuteInitialLayoutPass(scroller);
+
+ var last = (target.Items as IList)[10];
+
+ target.ScrollIntoView(last);
+
+ Assert.Equal(new Vector(0, 1), ((ILogicalScrollable)target).Offset);
+ Assert.Same(target.Panel.Children[9].DataContext, last);
+ }
+
+ [Fact]
+ public void Second_Scroll_To_Last_Should_Work()
+ {
+ var target = CreateTarget(itemCount: 11);
+ var scroller = (TestScroller)target.Parent;
+
+ scroller.Width = scroller.Height = 100;
+ scroller.LayoutManager.ExecuteInitialLayoutPass(scroller);
+
+ var last = (target.Items as IList)[10];
+
+ target.ScrollIntoView(last);
+
+ Assert.Equal(new Vector(0, 1), ((ILogicalScrollable)target).Offset);
+ Assert.Same(target.Panel.Children[9].DataContext, last);
+
+ target.ScrollIntoView(last);
+
+ Assert.Equal(new Vector(0, 1), ((ILogicalScrollable)target).Offset);
+ Assert.Same(target.Panel.Children[9].DataContext, last);
+ }
+
public class Vertical
{
[Fact]
@@ -1090,4 +1129,4 @@ namespace Avalonia.Controls.UnitTests.Presenters
}
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj b/tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj
index 1b2ba3c7de..ef515ce155 100644
--- a/tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj
+++ b/tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj
@@ -1,9 +1,11 @@
- netcoreapp2.0
+ netcoreapp2.0;net47
Library
+ true
+
@@ -22,4 +24,4 @@
-
\ No newline at end of file
+
diff --git a/tests/Avalonia.Interactivity.UnitTests/Avalonia.Interactivity.UnitTests.csproj b/tests/Avalonia.Interactivity.UnitTests/Avalonia.Interactivity.UnitTests.csproj
index bc883d0251..6b19d81034 100644
--- a/tests/Avalonia.Interactivity.UnitTests/Avalonia.Interactivity.UnitTests.csproj
+++ b/tests/Avalonia.Interactivity.UnitTests/Avalonia.Interactivity.UnitTests.csproj
@@ -1,9 +1,12 @@
netcoreapp2.0
+ netcoreapp2.0;net47
Library
+ true
+
@@ -20,4 +23,4 @@
-
\ No newline at end of file
+
diff --git a/tests/Avalonia.Markup.UnitTests/Avalonia.Markup.UnitTests.csproj b/tests/Avalonia.Markup.UnitTests/Avalonia.Markup.UnitTests.csproj
index 1680ecf798..aba5f2d469 100644
--- a/tests/Avalonia.Markup.UnitTests/Avalonia.Markup.UnitTests.csproj
+++ b/tests/Avalonia.Markup.UnitTests/Avalonia.Markup.UnitTests.csproj
@@ -1,9 +1,11 @@
- netcoreapp2.0
+ netcoreapp2.0;net47
Library
+ true
+
@@ -23,4 +25,4 @@
-
\ No newline at end of file
+
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj
index 735d5c421a..8b47bcec30 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj
@@ -1,9 +1,11 @@
- netcoreapp2.0
+ netcoreapp2.0;net47
Library
+ true
+
@@ -36,4 +38,4 @@
-
\ No newline at end of file
+
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Converters/NullableConverterTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Converters/NullableConverterTests.cs
new file mode 100644
index 0000000000..abe6fa84b0
--- /dev/null
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Converters/NullableConverterTests.cs
@@ -0,0 +1,31 @@
+using Avalonia.Controls;
+using Avalonia.UnitTests;
+using Xunit;
+
+namespace Avalonia.Markup.Xaml.UnitTests.Converters
+{
+ public class ClassWithNullableProperties
+ {
+ public Thickness? Thickness { get; set; }
+ public Orientation? Orientation { get; set; }
+ }
+
+ public class NullableConverterTests
+ {
+ [Fact]
+ public void Nullable_Types_Should_Still_Be_Converted_Properly()
+ {
+ using (UnitTestApplication.Start(TestServices.MockPlatformWrapper))
+ {
+ var xaml = @"";
+ var loader = new AvaloniaXamlLoader();
+ var data = (ClassWithNullableProperties)loader.Load(xaml, typeof(ClassWithNullableProperties).Assembly);
+ Assert.Equal(new Thickness(5), data.Thickness);
+ Assert.Equal(Orientation.Vertical, data.Orientation);
+ }
+ }
+ }
+}
diff --git a/tests/Avalonia.Styling.UnitTests/Avalonia.Styling.UnitTests.csproj b/tests/Avalonia.Styling.UnitTests/Avalonia.Styling.UnitTests.csproj
index e65dd848dd..3fd280f7ab 100644
--- a/tests/Avalonia.Styling.UnitTests/Avalonia.Styling.UnitTests.csproj
+++ b/tests/Avalonia.Styling.UnitTests/Avalonia.Styling.UnitTests.csproj
@@ -1,10 +1,12 @@
- netcoreapp2.0
+ netcoreapp2.0;net47
Library
CS0067
+ true
+
@@ -23,4 +25,4 @@
-
\ No newline at end of file
+
diff --git a/tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs b/tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs
index d837f2cb2f..0561837ad1 100644
--- a/tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs
+++ b/tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs
@@ -27,7 +27,7 @@ namespace Avalonia.Styling.UnitTests
var selector = default(Selector).OfType().Child().OfType();
- Assert.True(selector.Match(child).ImmediateResult);
+ Assert.Equal(SelectorMatchResult.AlwaysThisInstance, selector.Match(child).Result);
}
[Fact]
@@ -42,7 +42,7 @@ namespace Avalonia.Styling.UnitTests
var selector = default(Selector).OfType().Child().OfType();
- Assert.False(selector.Match(child).ImmediateResult);
+ Assert.Equal(SelectorMatchResult.NeverThisInstance, selector.Match(child).Result);
}
[Fact]
@@ -54,7 +54,7 @@ namespace Avalonia.Styling.UnitTests
child.LogicalParent = parent;
var selector = default(Selector).OfType().Class("foo").Child().OfType();
- var activator = selector.Match(child).ObservableResult;
+ var activator = selector.Match(child).Activator;
var result = new List();
Assert.False(await activator.Take(1));
@@ -70,7 +70,7 @@ namespace Avalonia.Styling.UnitTests
var control = new TestLogical3();
var selector = default(Selector).OfType().Child().OfType();
- Assert.False(selector.Match(control).ImmediateResult);
+ Assert.Equal(SelectorMatchResult.NeverThisInstance, selector.Match(control).Result);
}
[Fact]
diff --git a/tests/Avalonia.Styling.UnitTests/SelectorTests_Class.cs b/tests/Avalonia.Styling.UnitTests/SelectorTests_Class.cs
index 75599925b7..496998ecd9 100644
--- a/tests/Avalonia.Styling.UnitTests/SelectorTests_Class.cs
+++ b/tests/Avalonia.Styling.UnitTests/SelectorTests_Class.cs
@@ -40,9 +40,10 @@ namespace Avalonia.Styling.UnitTests
};
var target = default(Selector).Class("foo");
- var activator = target.Match(control).ObservableResult;
+ var match = target.Match(control);
- Assert.True(await activator.Take(1));
+ Assert.Equal(SelectorMatchResult.Sometimes, match.Result);
+ Assert.True(await match.Activator.Take(1));
}
[Fact]
@@ -54,9 +55,10 @@ namespace Avalonia.Styling.UnitTests
};
var target = default(Selector).Class("foo");
- var activator = target.Match(control).ObservableResult;
+ var match = target.Match(control);
- Assert.False(await activator.Take(1));
+ Assert.Equal(SelectorMatchResult.Sometimes, match.Result);
+ Assert.False(await match.Activator.Take(1));
}
[Fact]
@@ -69,9 +71,10 @@ namespace Avalonia.Styling.UnitTests
};
var target = default(Selector).Class("foo");
- var activator = target.Match(control).ObservableResult;
+ var match = target.Match(control);
- Assert.True(await activator.Take(1));
+ Assert.Equal(SelectorMatchResult.Sometimes, match.Result);
+ Assert.True(await match.Activator.Take(1));
}
[Fact]
@@ -80,7 +83,7 @@ namespace Avalonia.Styling.UnitTests
var control = new Control1();
var target = default(Selector).Class("foo");
- var activator = target.Match(control).ObservableResult;
+ var activator = target.Match(control).Activator;
Assert.False(await activator.Take(1));
control.Classes.Add("foo");
@@ -96,7 +99,7 @@ namespace Avalonia.Styling.UnitTests
};
var target = default(Selector).Class("foo");
- var activator = target.Match(control).ObservableResult;
+ var activator = target.Match(control).Activator;
Assert.True(await activator.Take(1));
control.Classes.Remove("foo");
@@ -108,7 +111,7 @@ namespace Avalonia.Styling.UnitTests
{
var control = new Control1();
var target = default(Selector).Class("foo").Class("bar");
- var activator = target.Match(control).ObservableResult;
+ var activator = target.Match(control).Activator;
Assert.False(await activator.Take(1));
control.Classes.Add("foo");
@@ -129,7 +132,7 @@ namespace Avalonia.Styling.UnitTests
};
var target = default(Selector).Class("foo");
- var activator = target.Match(control).ObservableResult;
+ var activator = target.Match(control).Activator;
var result = new List();
using (activator.Subscribe(x => result.Add(x)))
diff --git a/tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs b/tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs
index b75b59c212..56dad13186 100644
--- a/tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs
+++ b/tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs
@@ -26,7 +26,7 @@ namespace Avalonia.Styling.UnitTests
var selector = default(Selector).OfType().Descendant().OfType();
- Assert.True(selector.Match(child).ImmediateResult);
+ Assert.Equal(SelectorMatchResult.AlwaysThisInstance, selector.Match(child).Result);
}
[Fact]
@@ -41,7 +41,7 @@ namespace Avalonia.Styling.UnitTests
var selector = default(Selector).OfType().Descendant().OfType();
- Assert.True(selector.Match(child).ImmediateResult);
+ Assert.Equal(SelectorMatchResult.AlwaysThisInstance, selector.Match(child).Result);
}
[Fact]
@@ -56,7 +56,7 @@ namespace Avalonia.Styling.UnitTests
child.LogicalParent = parent;
var selector = default(Selector).OfType().Class("foo").Descendant().OfType();
- var activator = selector.Match(child).ObservableResult;
+ var activator = selector.Match(child).Activator;
Assert.True(await activator.Take(1));
}
@@ -74,7 +74,7 @@ namespace Avalonia.Styling.UnitTests
child.LogicalParent = parent;
var selector = default(Selector).OfType().Class("foo").Descendant().OfType();
- var activator = selector.Match(child).ObservableResult;
+ var activator = selector.Match(child).Activator;
Assert.False(await activator.Take(1));
}
@@ -90,7 +90,7 @@ namespace Avalonia.Styling.UnitTests
child.LogicalParent = parent;
var selector = default(Selector).OfType().Class("foo").Descendant().OfType();
- var activator = selector.Match(child).ObservableResult;
+ var activator = selector.Match(child).Activator;
Assert.False(await activator.Take(1));
parent.Classes.Add("foo");
diff --git a/tests/Avalonia.Styling.UnitTests/SelectorTests_Multiple.cs b/tests/Avalonia.Styling.UnitTests/SelectorTests_Multiple.cs
index 067b08b296..04b29376b0 100644
--- a/tests/Avalonia.Styling.UnitTests/SelectorTests_Multiple.cs
+++ b/tests/Avalonia.Styling.UnitTests/SelectorTests_Multiple.cs
@@ -14,7 +14,7 @@ namespace Avalonia.Styling.UnitTests
public class SelectorTests_Multiple
{
[Fact]
- public void Template_Child_Of_Control_With_Two_Classes()
+ public void Named_Template_Child_Of_Control_With_Two_Classes()
{
var template = new FuncControlTemplate(parent =>
{
@@ -40,9 +40,10 @@ namespace Avalonia.Styling.UnitTests
var border = (Border)((IVisual)control).VisualChildren.Single();
var values = new List();
- var activator = selector.Match(border).ObservableResult;
+ var match = selector.Match(border);
- activator.Subscribe(x => values.Add(x));
+ Assert.Equal(SelectorMatchResult.Sometimes, match.Result);
+ match.Activator.Subscribe(x => values.Add(x));
Assert.Equal(new[] { false }, values);
control.Classes.AddRange(new[] { "foo", "bar" });
@@ -51,6 +52,39 @@ namespace Avalonia.Styling.UnitTests
Assert.Equal(new[] { false, true, false }, values);
}
+ [Fact]
+ public void Named_OfType_Template_Child_Of_Control_With_Two_Classes_Wrong_Type()
+ {
+ var template = new FuncControlTemplate(parent =>
+ {
+ return new Border
+ {
+ Name = "border",
+ };
+ });
+
+ var control = new Button
+ {
+ Template = template,
+ };
+
+ control.ApplyTemplate();
+
+ var selector = default(Selector)
+ .OfType