Browse Source

Merge branch 'master' into togglebutton-ischecked-default-value

pull/1336/head
jp2masa 8 years ago
committed by GitHub
parent
commit
9dc7b6fe62
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .gitignore
  2. 3
      .ncrunch/Avalonia.Direct2D1.RenderTests.v3.ncrunchproject
  3. 2
      appveyor.yml
  4. 78
      build.cake
  5. 4
      parameters.cake
  6. 2
      samples/BindingTest/App.config
  7. 2
      samples/BindingTest/BindingTest.csproj
  8. 2
      samples/ControlCatalog.Desktop/App.config
  9. 2
      samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj
  10. 2
      samples/RenderTest/App.config
  11. 2
      samples/RenderTest/RenderTest.csproj
  12. 2
      samples/VirtualizationTest/App.config
  13. 2
      samples/VirtualizationTest/VirtualizationTest.csproj
  14. 1
      scripts/ReplaceNugetCache.ps1
  15. 2
      scripts/ReplaceNugetCache.sh
  16. 3
      scripts/ReplaceNugetCacheRelease.ps1
  17. 7
      src/Android/Avalonia.Android/Resources/Resource.Designer.cs
  18. 2
      src/Android/Avalonia.AndroidTestApplication/MainActivity.cs
  19. 4
      src/Avalonia.Animation/Avalonia.Animation.csproj
  20. 4
      src/Avalonia.Base/Avalonia.Base.csproj
  21. 151
      src/Avalonia.Base/AvaloniaObject.cs
  22. 24
      src/Avalonia.Base/AvaloniaObjectExtensions.cs
  23. 5
      src/Avalonia.Base/Collections/AvaloniaList.cs
  24. 15
      src/Avalonia.Base/Collections/IAvaloniaList.cs
  25. 7
      src/Avalonia.Base/IAvaloniaObject.cs
  26. 46
      src/Avalonia.Base/PriorityBindingEntry.cs
  27. 63
      src/Avalonia.Base/PriorityValue.cs
  28. 16
      src/Avalonia.Base/Reactive/AnonymousSubject`1.cs
  29. 49
      src/Avalonia.Base/Reactive/AnonymousSubject`2.cs
  30. 156
      src/Avalonia.Base/Utilities/DeferredSetter.cs
  31. 20
      src/Avalonia.Controls/AppBuilderBase.cs
  32. 9
      src/Avalonia.Controls/Application.cs
  33. 4
      src/Avalonia.Controls/Avalonia.Controls.csproj
  34. 2
      src/Avalonia.Controls/Calendar/Calendar.cs
  35. 1
      src/Avalonia.Controls/ContentControl.cs
  36. 15
      src/Avalonia.Controls/Control.cs
  37. 33
      src/Avalonia.Controls/DropDown.cs
  38. 4
      src/Avalonia.Controls/IPanel.cs
  39. 11
      src/Avalonia.Controls/ItemsControl.cs
  40. 43
      src/Avalonia.Controls/Panel.cs
  41. 15
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  42. 31
      src/Avalonia.Controls/Primitives/PopupRoot.cs
  43. 4
      src/Avalonia.Controls/Primitives/RangeBase.cs
  44. 72
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  45. 12
      src/Avalonia.Controls/Primitives/Track.cs
  46. 3
      src/Avalonia.Controls/ProgressBar.cs
  47. 8
      src/Avalonia.Controls/TextBox.cs
  48. 5
      src/Avalonia.Controls/TreeView.cs
  49. 8
      src/Avalonia.Controls/VirtualizingStackPanel.cs
  50. 4
      src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj
  51. 6
      src/Avalonia.Diagnostics/Views/TreePage.xaml.cs
  52. 2
      src/Avalonia.DotNetCoreRuntime/Avalonia.DotNetCoreRuntime.csproj
  53. 4
      src/Avalonia.Input/Avalonia.Input.csproj
  54. 17
      src/Avalonia.Input/KeyGesture.cs
  55. 4
      src/Avalonia.Interactivity/Avalonia.Interactivity.csproj
  56. 4
      src/Avalonia.Layout/Avalonia.Layout.csproj
  57. 4
      src/Avalonia.Logging.Serilog/Avalonia.Logging.Serilog.csproj
  58. 4
      src/Avalonia.Styling/Avalonia.Styling.csproj
  59. 4
      src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj
  60. 2
      src/Avalonia.Themes.Default/Button.xaml
  61. 2
      src/Avalonia.Themes.Default/CalendarButton.xaml
  62. 2
      src/Avalonia.Themes.Default/CalendarDayButton.xaml
  63. 2
      src/Avalonia.Themes.Default/CheckBox.xaml
  64. 2
      src/Avalonia.Themes.Default/ContentControl.xaml
  65. 2
      src/Avalonia.Themes.Default/DropDownItem.xaml
  66. 2
      src/Avalonia.Themes.Default/EmbeddableControlRoot.xaml
  67. 8
      src/Avalonia.Themes.Default/Expander.xaml
  68. 2
      src/Avalonia.Themes.Default/LayoutTransformControl.xaml
  69. 2
      src/Avalonia.Themes.Default/ListBoxItem.xaml
  70. 2
      src/Avalonia.Themes.Default/PopupRoot.xaml
  71. 2
      src/Avalonia.Themes.Default/RadioButton.xaml
  72. 2
      src/Avalonia.Themes.Default/RepeatButton.xaml
  73. 2
      src/Avalonia.Themes.Default/TabStripItem.xaml
  74. 2
      src/Avalonia.Themes.Default/ToggleButton.xaml
  75. 2
      src/Avalonia.Themes.Default/ToolTip.xaml
  76. 2
      src/Avalonia.Themes.Default/Window.xaml
  77. 2
      src/Avalonia.Visuals/Media/PathMarkupParser.cs
  78. 50
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  79. 2
      src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
  80. 17
      src/Avalonia.Visuals/Rendering/RendererBase.cs
  81. 6
      src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs
  82. 17
      src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
  83. 30
      src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
  84. 13
      src/Avalonia.Visuals/Visual.cs
  85. 3
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  86. 14
      src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoaderPortableXaml.cs
  87. 109
      src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs
  88. 2
      src/Markup/Avalonia.Markup.Xaml/Data/RelativeSource.cs
  89. 153
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs
  90. 2
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs
  91. 2
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github
  92. 24
      src/Markup/Avalonia.Markup.Xaml/XamlLoadException.cs
  93. 4
      src/Markup/Avalonia.Markup/Avalonia.Markup.csproj
  94. 91
      src/Markup/Avalonia.Markup/ControlLocator.cs
  95. 10
      src/Markup/Avalonia.Markup/Data/Parsers/ArgumentListParser.cs
  96. 4
      src/Skia/Avalonia.Skia/FormattedTextImpl.cs
  97. 7
      src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs
  98. 1
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  99. 7
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs
  100. 4
      src/Windows/Avalonia.Win32/Avalonia.Win32.csproj

2
.gitignore

@ -176,3 +176,5 @@ nuget
Avalonia.XBuild.sln
project.lock.json
.idea/*
**/obj-Skia/*
**/obj-Direct2D1/*

3
.ncrunch/Avalonia.Direct2D1.RenderTests.v3.ncrunchproject

@ -1,7 +1,6 @@
<ProjectConfiguration>
<Settings>
<DefaultTestTimeout>1000</DefaultTestTimeout>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
<DefaultTestTimeout>3000</DefaultTestTimeout>
<PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully>
</Settings>
</ProjectConfiguration>

2
appveyor.yml

@ -1,4 +1,4 @@
os: Previous Visual Studio 2017
os: Visual Studio 2017
platform:
- Any CPU
skip_branch_with_pr: true

78
build.cake

@ -93,7 +93,7 @@ Task("Clean")
CleanDirectory(parameters.NugetRoot);
CleanDirectory(parameters.ZipRoot);
CleanDirectory(parameters.BinRoot);
CleanDirectory(parameters.TestsRoot);
CleanDirectory(parameters.DesignerTestsRoot);
});
Task("Restore-NuGet-Packages")
@ -124,14 +124,9 @@ Task("Restore-NuGet-Packages")
void DotNetCoreBuild()
{
DotNetCoreRestore("samples\\ControlCatalog.NetCore");
DotNetBuild("samples\\ControlCatalog.NetCore");
DotNetCoreBuild("samples\\ControlCatalog.NetCore");
}
Task("DotNetCoreBuild")
.IsDependentOn("Clean")
.Does(() => DotNetCoreBuild());
Task("Build")
.IsDependentOn("Restore-NuGet-Packages")
.Does(() =>
@ -140,11 +135,11 @@ Task("Build")
{
MSBuild(parameters.MSBuildSolution, settings => {
settings.SetConfiguration(parameters.Configuration);
settings.SetVerbosity(Verbosity.Minimal);
settings.WithProperty("Platform", "\"" + parameters.Platform + "\"");
settings.WithProperty("UseRoslynPathHack", "true");
settings.SetVerbosity(Verbosity.Minimal);
settings.WithProperty("Windows", "True");
settings.UseToolVersion(MSBuildToolVersion.VS2017);
settings.WithProperty("Windows", "True");
settings.SetNodeReuse(false);
});
}
@ -160,10 +155,9 @@ void RunCoreTest(string project, Parameters parameters, bool coreOnly = false)
if(!project.EndsWith(".csproj"))
project = System.IO.Path.Combine(project, System.IO.Path.GetFileName(project)+".csproj");
Information("Running tests from " + project);
DotNetCoreRestore(project);
var frameworks = new List<string>(){"netcoreapp2.0"};
if(parameters.IsRunningOnWindows)
frameworks.Add("net461");
frameworks.Add("net47");
foreach(var fw in frameworks)
{
if(!fw.StartsWith("netcoreapp") && coreOnly)
@ -178,8 +172,11 @@ void RunCoreTest(string project, Parameters parameters, bool coreOnly = false)
}
}
Task("Run-Net-Core-Unit-Tests")
.IsDependentOn("Clean")
Task("Run-Unit-Tests")
.IsDependentOn("Build")
.IsDependentOn("Run-Designer-Unit-Tests")
.IsDependentOn("Run-Render-Tests")
.WithCriteria(() => !parameters.SkipTests)
.Does(() => {
RunCoreTest("./tests/Avalonia.Base.UnitTests", parameters, false);
RunCoreTest("./tests/Avalonia.Controls.UnitTests", parameters, false);
@ -190,27 +187,25 @@ Task("Run-Net-Core-Unit-Tests")
RunCoreTest("./tests/Avalonia.Markup.Xaml.UnitTests", parameters, false);
RunCoreTest("./tests/Avalonia.Styling.UnitTests", parameters, false);
RunCoreTest("./tests/Avalonia.Visuals.UnitTests", parameters, false);
if(parameters.IsRunningOnWindows)
RunCoreTest("./tests/Avalonia.RenderTests/Avalonia.Skia.RenderTests.csproj", parameters, true);
if (parameters.IsRunningOnWindows)
{
RunCoreTest("./tests/Avalonia.Direct2D1.UnitTests", parameters, true);
}
});
Task("Run-Unit-Tests")
.IsDependentOn("Run-Net-Core-Unit-Tests")
Task("Run-Render-Tests")
.IsDependentOn("Build")
//.IsDependentOn("Run-Leak-Tests")
.WithCriteria(() => !parameters.SkipTests)
.WithCriteria(() => !parameters.SkipTests && parameters.IsRunningOnWindows)
.Does(() => {
RunCoreTest("./tests/Avalonia.RenderTests/Avalonia.Skia.RenderTests.csproj", parameters, true);
RunCoreTest("./tests/Avalonia.RenderTests/Avalonia.Direct2D1.RenderTests.csproj", parameters, true);
});
Task("Run-Designer-Unit-Tests")
.IsDependentOn("Build")
.WithCriteria(() => !parameters.SkipTests && parameters.IsRunningOnWindows)
.Does(() =>
{
if(!parameters.IsRunningOnWindows)
return;
var unitTests = GetDirectories("./tests/Avalonia.*.UnitTests")
.Select(dir => System.IO.Path.GetFileName(dir.FullPath))
.Where( name => !name.Contains("Skia")) // Run in the Run-Net-Core-Unit-Tests target
.Where(name => parameters.IsRunningOnWindows ? true : !name.Contains("Direct2D"))
.Select(name => MakeAbsolute(File("./tests/" + name + "/bin/" + parameters.DirSuffix + "/" + name + ".dll")))
.ToList();
var toolPath = (parameters.IsPlatformAnyCPU || parameters.IsPlatformX86) ?
Context.Tools.Resolve("xunit.console.x86.exe") :
Context.Tools.Resolve("xunit.console.exe");
@ -219,27 +214,10 @@ Task("Run-Unit-Tests")
{
ToolPath = toolPath,
Parallelism = ParallelismOption.None,
ShadowCopy = false
ShadowCopy = false,
};
xUnitSettings.NoAppDomain = !parameters.IsRunningOnWindows;
foreach(var test in unitTests.Where(testFile => FileExists(testFile)))
{
CopyDirectory(test.GetDirectory(), parameters.TestsRoot);
}
var testsInDirectoryToRun = new List<FilePath>();
if(parameters.IsRunningOnWindows)
{
testsInDirectoryToRun.AddRange(GetFiles("./artifacts/tests/*Tests.dll"));
}
else
{
testsInDirectoryToRun.AddRange(GetFiles("./artifacts/tests/*.UnitTests.dll"));
}
XUnit2(testsInDirectoryToRun, xUnitSettings);
XUnit2("./artifacts/designer-tests/Avalonia.DesignerSupport.Tests.dll", xUnitSettings);
});
Task("Copy-Files")
@ -427,7 +405,7 @@ Task("Default").Does(() =>
if(parameters.IsRunningOnWindows)
RunTarget("Package");
else
RunTarget("Run-Net-Core-Unit-Tests");
RunTarget("Run-Unit-Tests");
});
Task("AppVeyor")
.IsDependentOn("Zip-Files")
@ -435,7 +413,7 @@ Task("AppVeyor")
.IsDependentOn("Publish-NuGet");
Task("Travis")
.IsDependentOn("Run-Net-Core-Unit-Tests");
.IsDependentOn("Run-Unit-Tests");
///////////////////////////////////////////////////////////////////////////////
// EXECUTE

4
parameters.cake

@ -30,7 +30,7 @@ public class Parameters
public DirectoryPath NugetRoot { get; private set; }
public DirectoryPath ZipRoot { get; private set; }
public DirectoryPath BinRoot { get; private set; }
public DirectoryPath TestsRoot { get; private set; }
public DirectoryPath DesignerTestsRoot { get; private set; }
public string DirSuffix { get; private set; }
public string DirSuffixIOS { get; private set; }
public DirectoryPathCollection BuildDirs { get; private set; }
@ -106,7 +106,7 @@ public class Parameters
NugetRoot = ArtifactsDir.Combine("nuget");
ZipRoot = ArtifactsDir.Combine("zip");
BinRoot = ArtifactsDir.Combine("bin");
TestsRoot = ArtifactsDir.Combine("tests");
DesignerTestsRoot = ArtifactsDir.Combine("designer-tests");
BuildDirs = context.GetDirectories("**/bin") + context.GetDirectories("**/obj");

2
samples/BindingTest/App.config

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1"/>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7"/>
</startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">

2
samples/BindingTest/BindingTest.csproj

@ -9,7 +9,7 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>BindingTest</RootNamespace>
<AssemblyName>BindingTest</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<TargetFrameworkProfile />

2
samples/ControlCatalog.Desktop/App.config

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1"/>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7"/>
</startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">

2
samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj

@ -9,7 +9,7 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ControlCatalog.Desktop</RootNamespace>
<AssemblyName>ControlCatalog.Desktop</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<TargetFrameworkProfile />

2
samples/RenderTest/App.config

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1"/>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7"/>
</startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">

2
samples/RenderTest/RenderTest.csproj

@ -9,7 +9,7 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>RenderTest</RootNamespace>
<AssemblyName>RenderTest</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<TargetFrameworkProfile />

2
samples/VirtualizationTest/App.config

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1"/>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7"/>
</startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">

2
samples/VirtualizationTest/VirtualizationTest.csproj

@ -9,7 +9,7 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>VirtualizationTest</RootNamespace>
<AssemblyName>VirtualizationTest</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<TargetFrameworkProfile />

1
scripts/ReplaceNugetCache.ps1

@ -2,3 +2,4 @@ copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia**.dll ~\
copy ..\samples\ControlCatalog.NetCore.\bin\Debug\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netstandard2.0\
copy ..\samples\ControlCatalog.NetCore.\bin\Debug\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia.gtk3\$args\lib\netstandard2.0\
copy ..\samples\ControlCatalog.NetCore.\bin\Debug\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia.win32\$args\lib\netstandard2.0\
copy ..\samples\ControlCatalog.NetCore.\bin\Debug\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia.skia\$args\lib\netstandard2.0\

2
scripts/ReplaceNugetCache.sh

@ -3,4 +3,6 @@
cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp2.0/Avalonia**.dll ~/.nuget/packages/avalonia/$1/lib/netcoreapp2.0/
cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp2.0/Avalonia**.dll ~/.nuget/packages/avalonia/$1/lib/netstandard2.0/
cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp2.0/Avalonia**.dll ~/.nuget/packages/avalonia.gtk3/$1/lib/netstandard2.0/
cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp2.0/Avalonia**.dll ~/.nuget/packages/avalonia.skia/$1/lib/netstandard2.0/

3
scripts/ReplaceNugetCacheRelease.ps1

@ -1,4 +1,5 @@
copy ..\samples\ControlCatalog.NetCore\bin\Release\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netcoreapp2.0\
copy ..\samples\ControlCatalog.NetCore.\bin\Release\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netstandard2.0\
copy ..\samples\ControlCatalog.NetCore.\bin\Release\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia.gtk3\$args\lib\netstandard2.0\
copy ..\samples\ControlCatalog.NetCore.\bin\Release\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia.win32\$args\lib\netstandard2.0\
copy ..\samples\ControlCatalog.NetCore.\bin\Release\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia.win32\$args\lib\netstandard2.0\
copy ..\samples\ControlCatalog.NetCore.\bin\Release\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia.skia\$args\lib\netstandard2.0\

7
src/Android/Avalonia.Android/Resources/Resource.Designer.cs

@ -40,14 +40,11 @@ namespace Avalonia.Android
public partial class String
{
// aapt resource value: 0x7f020002
public static int ApplicationName = 2130837506;
// aapt resource value: 0x7f020001
public static int Hello = 2130837505;
public static int ApplicationName = 2130837505;
// aapt resource value: 0x7f020000
public static int library_name = 2130837504;
public static int Hello = 2130837504;
static String()
{

2
src/Android/Avalonia.AndroidTestApplication/MainActivity.cs

@ -56,7 +56,7 @@ namespace Avalonia.AndroidTestApplication
{
Margin = new Thickness(30),
Background = Brushes.Yellow,
Children = new Avalonia.Controls.Controls
Children =
{
new TextBlock
{

4
src/Avalonia.Animation/Avalonia.Animation.csproj

@ -11,7 +11,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Debug\Avalonia.Animation.XML</DocumentationFile>
<DocumentationFile>bin\Debug\Avalonia.Animation.xml</DocumentationFile>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
@ -21,7 +21,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Release\Avalonia.Animation.XML</DocumentationFile>
<DocumentationFile>bin\Release\Avalonia.Animation.xml</DocumentationFile>
<NoWarn>CS1591</NoWarn>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>

4
src/Avalonia.Base/Avalonia.Base.csproj

@ -12,7 +12,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Debug\Avalonia.Base.XML</DocumentationFile>
<DocumentationFile>bin\Debug\Avalonia.Base.xml</DocumentationFile>
<NoWarn>CS1591</NoWarn>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
@ -22,7 +22,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Release\Avalonia.Base.XML</DocumentationFile>
<DocumentationFile>bin\Release\Avalonia.Base.xml</DocumentationFile>
<NoWarn>CS1591</NoWarn>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>

151
src/Avalonia.Base/AvaloniaObject.cs

@ -51,6 +51,21 @@ namespace Avalonia
/// </summary>
private EventHandler<AvaloniaPropertyChangedEventArgs> _propertyChanged;
private DeferredSetter<AvaloniaProperty, object> _directDeferredSetter;
/// <summary>
/// Delayed setter helper for direct properties. Used to fix #855.
/// </summary>
private DeferredSetter<AvaloniaProperty, object> DirectPropertyDeferredSetter
{
get
{
return _directDeferredSetter ??
(_directDeferredSetter = new DeferredSetter<AvaloniaProperty, object>());
}
}
/// <summary>
/// Initializes a new instance of the <see cref="AvaloniaObject"/> class.
/// </summary>
@ -225,6 +240,19 @@ namespace Avalonia
return (T)GetValue((AvaloniaProperty)property);
}
/// <summary>
/// Checks whether a <see cref="AvaloniaProperty"/> is animating.
/// </summary>
/// <param name="property">The property.</param>
/// <returns>True if the property is animating, otherwise false.</returns>
public bool IsAnimating(AvaloniaProperty property)
{
Contract.Requires<ArgumentNullException>(property != null);
VerifyAccess();
return _values.TryGetValue(property, out PriorityValue value) ? value.IsAnimating : false;
}
/// <summary>
/// Checks whether a <see cref="AvaloniaProperty"/> is set on this object.
/// </summary>
@ -311,9 +339,6 @@ namespace Avalonia
var description = GetDescription(source);
var scheduler = AvaloniaLocator.Current.GetService<IScheduler>() ?? ImmediateScheduler.Instance;
source = source.ObserveOn(scheduler);
if (property.IsDirect)
{
if (property.IsReadOnly)
@ -539,6 +564,45 @@ namespace Avalonia
}
}
/// <summary>
/// A callback type for encapsulating complex logic for setting direct properties.
/// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="value">The value to which to set the property.</param>
/// <param name="field">The backing field for the property.</param>
/// <param name="notifyWrapper">A wrapper for the property-changed notification.</param>
protected delegate void SetAndRaiseCallback<T>(T value, ref T field, Action<Action> notifyWrapper);
/// <summary>
/// Sets the backing field for a direct avalonia property, raising the
/// <see cref="PropertyChanged"/> event if the value has changed.
/// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param>
/// <param name="field">The backing field.</param>
/// <param name="setterCallback">A callback called to actually set the value to the backing field.</param>
/// <param name="value">The value.</param>
/// <returns>
/// True if the value changed, otherwise false.
/// </returns>
protected bool SetAndRaise<T>(
AvaloniaProperty<T> property,
ref T field,
SetAndRaiseCallback<T> setterCallback,
T value)
{
Contract.Requires<ArgumentNullException>(setterCallback != null);
return DirectPropertyDeferredSetter.SetAndNotify(
property,
ref field,
(object val, ref T backing, Action<Action> notify) =>
{
setterCallback((T)val, ref backing, notify);
return true;
},
value);
}
/// <summary>
/// Sets the backing field for a direct avalonia property, raising the
/// <see cref="PropertyChanged"/> event if the value has changed.
@ -553,17 +617,32 @@ namespace Avalonia
protected bool SetAndRaise<T>(AvaloniaProperty<T> property, ref T field, T value)
{
VerifyAccess();
if (!object.Equals(field, value))
{
var old = field;
field = value;
RaisePropertyChanged(property, old, value, BindingPriority.LocalValue);
return true;
}
else
{
return false;
}
return SetAndRaise(
property,
ref field,
(T val, ref T backing, Action<Action> notifyWrapper)
=> SetAndRaiseCore(property, ref backing, val, notifyWrapper),
value);
}
/// <summary>
/// Default assignment logic for SetAndRaise.
/// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param>
/// <param name="field">The backing field.</param>
/// <param name="value">The value.</param>
/// <param name="notifyWrapper">A wrapper for the property-changed notification.</param>
/// <returns>
/// True if the value changed, otherwise false.
/// </returns>
private bool SetAndRaiseCore<T>(AvaloniaProperty property, ref T field, T value, Action<Action> notifyWrapper)
{
var old = field;
field = value;
notifyWrapper(() => RaisePropertyChanged(property, old, value, BindingPriority.LocalValue));
return true;
}
/// <summary>
@ -661,29 +740,41 @@ namespace Avalonia
/// <param name="value">The value.</param>
private void SetDirectValue(AvaloniaProperty property, object value)
{
var notification = value as BindingNotification;
if (notification != null)
void Set()
{
notification.LogIfError(this, property);
value = notification.Value;
}
var notification = value as BindingNotification;
if (notification == null || notification.ErrorType == BindingErrorType.Error || notification.HasValue)
{
var metadata = (IDirectPropertyMetadata)property.GetMetadata(GetType());
var accessor = (IDirectPropertyAccessor)GetRegistered(property);
var finalValue = value == AvaloniaProperty.UnsetValue ?
metadata.UnsetValue : value;
if (notification != null)
{
notification.LogIfError(this, property);
value = notification.Value;
}
LogPropertySet(property, value, BindingPriority.LocalValue);
if (notification == null || notification.ErrorType == BindingErrorType.Error || notification.HasValue)
{
var metadata = (IDirectPropertyMetadata)property.GetMetadata(GetType());
var accessor = (IDirectPropertyAccessor)GetRegistered(property);
var finalValue = value == AvaloniaProperty.UnsetValue ?
metadata.UnsetValue : value;
accessor.SetValue(this, finalValue);
LogPropertySet(property, value, BindingPriority.LocalValue);
accessor.SetValue(this, finalValue);
}
if (notification != null)
{
UpdateDataValidation(property, notification);
}
}
if (notification != null)
if (Dispatcher.UIThread.CheckAccess())
{
Set();
}
else
{
UpdateDataValidation(property, notification);
Dispatcher.UIThread.InvokeAsync(Set);
}
}

24
src/Avalonia.Base/AvaloniaObjectExtensions.cs

@ -138,17 +138,9 @@ namespace Avalonia
AvaloniaProperty property,
BindingPriority priority = BindingPriority.LocalValue)
{
// TODO: Subject.Create<T> is not yet in stable Rx : once it is, remove the
// AnonymousSubject classes and use Subject.Create<T>.
var output = new Subject<object>();
var result = new AnonymousSubject<object>(
Observer.Create<object>(
x => output.OnNext(x),
e => output.OnError(e),
() => output.OnCompleted()),
return Subject.Create<object>(
Observer.Create<object>(x => o.SetValue(property, x, priority)),
o.GetObservable(property));
o.Bind(property, output, priority);
return result;
}
/// <summary>
@ -169,17 +161,9 @@ namespace Avalonia
AvaloniaProperty<T> property,
BindingPriority priority = BindingPriority.LocalValue)
{
// TODO: Subject.Create<T> is not yet in stable Rx : once it is, remove the
// AnonymousSubject classes from this file and use Subject.Create<T>.
var output = new Subject<T>();
var result = new AnonymousSubject<T>(
Observer.Create<T>(
x => output.OnNext(x),
e => output.OnError(e),
() => output.OnCompleted()),
return Subject.Create<T>(
Observer.Create<T>(x => o.SetValue(property, x, priority)),
o.GetObservable(property));
o.Bind(property, output, priority);
return result;
}
/// <summary>

5
src/Avalonia.Base/Collections/AvaloniaList.cs

@ -350,14 +350,15 @@ namespace Avalonia.Collections
public void MoveRange(int oldIndex, int count, int newIndex)
{
var items = _inner.GetRange(oldIndex, count);
var modifiedNewIndex = newIndex;
_inner.RemoveRange(oldIndex, count);
if (newIndex > oldIndex)
{
newIndex -= count;
modifiedNewIndex -= count;
}
_inner.InsertRange(newIndex, items);
_inner.InsertRange(modifiedNewIndex, items);
if (_collectionChanged != null)
{

15
src/Avalonia.Base/Collections/IAvaloniaList.cs

@ -36,6 +36,21 @@ namespace Avalonia.Collections
/// <param name="items">The items.</param>
void InsertRange(int index, IEnumerable<T> items);
/// <summary>
/// Moves an item to a new index.
/// </summary>
/// <param name="oldIndex">The index of the item to move.</param>
/// <param name="newIndex">The index to move the item to.</param>
void Move(int oldIndex, int newIndex);
/// <summary>
/// Moves multiple items to a new index.
/// </summary>
/// <param name="oldIndex">The first index of the items to move.</param>
/// <param name="count">The number of items to move.</param>
/// <param name="newIndex">The index to move the items to.</param>
void MoveRange(int oldIndex, int count, int newIndex);
/// <summary>
/// Removes multiple items from the collection.
/// </summary>

7
src/Avalonia.Base/IAvaloniaObject.cs

@ -31,6 +31,13 @@ namespace Avalonia
/// <returns>The value.</returns>
T GetValue<T>(AvaloniaProperty<T> property);
/// <summary>
/// Checks whether a <see cref="AvaloniaProperty"/> is animating.
/// </summary>
/// <param name="property">The property.</param>
/// <returns>True if the property is animating, otherwise false.</returns>
bool IsAnimating(AvaloniaProperty property);
/// <summary>
/// Checks whether a <see cref="AvaloniaProperty"/> is set on this object.
/// </summary>

46
src/Avalonia.Base/PriorityBindingEntry.cs

@ -3,6 +3,7 @@
using System;
using Avalonia.Data;
using Avalonia.Threading;
namespace Avalonia
{
@ -92,33 +93,50 @@ namespace Avalonia
private void ValueChanged(object value)
{
_owner.Owner.Owner?.VerifyAccess();
var notification = value as BindingNotification;
if (notification != null)
void Signal()
{
if (notification.HasValue || notification.ErrorType == BindingErrorType.Error)
var notification = value as BindingNotification;
if (notification != null)
{
Value = notification.Value;
_owner.Changed(this);
if (notification.HasValue || notification.ErrorType == BindingErrorType.Error)
{
Value = notification.Value;
_owner.Changed(this);
}
if (notification.ErrorType != BindingErrorType.None)
{
_owner.Error(this, notification);
}
}
if (notification.ErrorType != BindingErrorType.None)
else
{
_owner.Error(this, notification);
Value = value;
_owner.Changed(this);
}
}
if (Dispatcher.UIThread.CheckAccess())
{
Signal();
}
else
{
Value = value;
_owner.Changed(this);
Dispatcher.UIThread.InvokeAsync(Signal);
}
}
private void Completed()
{
_owner.Completed(this);
if (Dispatcher.UIThread.CheckAccess())
{
_owner.Completed(this);
}
else
{
Dispatcher.UIThread.InvokeAsync(() => _owner.Completed(this));
}
}
}
}

63
src/Avalonia.Base/PriorityValue.cs

@ -28,8 +28,10 @@ namespace Avalonia
{
private readonly Type _valueType;
private readonly SingleOrDictionary<int, PriorityLevel> _levels = new SingleOrDictionary<int, PriorityLevel>();
private object _value;
private readonly Func<object, object> _validate;
private static readonly DeferredSetter<PriorityValue, (object value, int priority)> delayedSetter = new DeferredSetter<PriorityValue, (object, int)>();
private (object value, int priority) _value;
/// <summary>
/// Initializes a new instance of the <see cref="PriorityValue"/> class.
@ -47,11 +49,22 @@ namespace Avalonia
Owner = owner;
Property = property;
_valueType = valueType;
_value = AvaloniaProperty.UnsetValue;
ValuePriority = int.MaxValue;
_value = (AvaloniaProperty.UnsetValue, int.MaxValue);
_validate = validate;
}
/// <summary>
/// Gets a value indicating whether the property is animating.
/// </summary>
public bool IsAnimating
{
get
{
return ValuePriority <= (int)BindingPriority.Animation &&
GetLevel(ValuePriority).ActiveBindingIndex != -1;
}
}
/// <summary>
/// Gets the owner of the value.
/// </summary>
@ -65,16 +78,12 @@ namespace Avalonia
/// <summary>
/// Gets the current value.
/// </summary>
public object Value => _value;
public object Value => _value.value;
/// <summary>
/// Gets the priority of the binding that is currently active.
/// </summary>
public int ValuePriority
{
get;
private set;
}
public int ValuePriority => _value.priority;
/// <summary>
/// Adds a new binding.
@ -234,25 +243,36 @@ namespace Avalonia
/// <param name="priority">The priority level that the value came from.</param>
private void UpdateValue(object value, int priority)
{
var notification = value as BindingNotification;
delayedSetter.SetAndNotify(this,
ref _value,
UpdateCore,
(value, priority));
}
private bool UpdateCore(
(object value, int priority) update,
ref (object value, int priority) backing,
Action<Action> notify)
{
var val = update.value;
var notification = val as BindingNotification;
object castValue;
if (notification != null)
{
value = (notification.HasValue) ? notification.Value : null;
val = (notification.HasValue) ? notification.Value : null;
}
if (TypeUtilities.TryConvertImplicit(_valueType, value, out castValue))
if (TypeUtilities.TryConvertImplicit(_valueType, val, out castValue))
{
var old = _value;
var old = backing.value;
if (_validate != null && castValue != AvaloniaProperty.UnsetValue)
{
castValue = _validate(castValue);
}
ValuePriority = priority;
_value = castValue;
backing = (castValue, update.priority);
if (notification?.HasValue == true)
{
@ -261,7 +281,7 @@ namespace Avalonia
if (notification == null || notification.HasValue)
{
Owner?.Changed(this, old, _value);
notify(() => Owner?.Changed(this, old, Value));
}
if (notification != null)
@ -272,14 +292,15 @@ namespace Avalonia
else
{
Logger.Error(
LogArea.Binding,
LogArea.Binding,
Owner,
"Binding produced invalid value for {$Property} ({$PropertyType}): {$Value} ({$ValueType})",
Property.Name,
_valueType,
value,
value?.GetType());
Property.Name,
_valueType,
val,
val?.GetType());
}
return true;
}
}
}

16
src/Avalonia.Base/Reactive/AnonymousSubject`1.cs

@ -1,16 +0,0 @@
// 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;
using System.Reactive.Subjects;
namespace Avalonia.Reactive
{
public class AnonymousSubject<T> : AnonymousSubject<T, T>, ISubject<T>
{
public AnonymousSubject(IObserver<T> observer, IObservable<T> observable)
: base(observer, observable)
{
}
}
}

49
src/Avalonia.Base/Reactive/AnonymousSubject`2.cs

@ -1,49 +0,0 @@
// 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;
using System.Reactive.Subjects;
namespace Avalonia.Reactive
{
public class AnonymousSubject<T, U> : ISubject<T, U>
{
private readonly IObserver<T> _observer;
private readonly IObservable<U> _observable;
public AnonymousSubject(IObserver<T> observer, IObservable<U> observable)
{
_observer = observer;
_observable = observable;
}
public void OnCompleted()
{
_observer.OnCompleted();
}
public void OnError(Exception error)
{
if (error == null)
throw new ArgumentNullException("error");
_observer.OnError(error);
}
public void OnNext(T value)
{
_observer.OnNext(value);
}
public IDisposable Subscribe(IObserver<U> observer)
{
if (observer == null)
throw new ArgumentNullException("observer");
//
// [OK] Use of unsafe Subscribe: non-pretentious wrapping of an observable sequence.
//
return _observable.Subscribe/*Unsafe*/(observer);
}
}
}

156
src/Avalonia.Base/Utilities/DeferredSetter.cs

@ -0,0 +1,156 @@
using System;
using System.Collections.Generic;
using System.Reactive.Disposables;
using System.Runtime.CompilerServices;
using System.Text;
namespace Avalonia.Utilities
{
/// <summary>
/// A utility class to enable deferring assignment until after property-changed notifications are sent.
/// </summary>
/// <typeparam name="TProperty">The type of the object that represents the property.</typeparam>
/// <typeparam name="TSetRecord">The type of value with which to track the delayed assignment.</typeparam>
class DeferredSetter<TProperty, TSetRecord>
where TProperty: class
{
private struct NotifyDisposable : IDisposable
{
private readonly SettingStatus status;
internal NotifyDisposable(SettingStatus status)
{
this.status = status;
status.Notifying = true;
}
public void Dispose()
{
status.Notifying = false;
}
}
/// <summary>
/// Information on current setting/notification status of a property.
/// </summary>
private class SettingStatus
{
public bool Notifying { get; set; }
private Queue<TSetRecord> pendingValues;
public Queue<TSetRecord> PendingValues
{
get
{
return pendingValues ?? (pendingValues = new Queue<TSetRecord>());
}
}
}
private readonly ConditionalWeakTable<TProperty, SettingStatus> setRecords = new ConditionalWeakTable<TProperty, SettingStatus>();
/// <summary>
/// Mark the property as currently notifying.
/// </summary>
/// <param name="property">The property to mark as notifying.</param>
/// <returns>Returns a disposable that when disposed, marks the property as done notifying.</returns>
private NotifyDisposable MarkNotifying(TProperty property)
{
Contract.Requires<InvalidOperationException>(!IsNotifying(property));
return new NotifyDisposable(setRecords.GetOrCreateValue(property));
}
/// <summary>
/// Check if the property is currently notifying listeners.
/// </summary>
/// <param name="property">The property.</param>
/// <returns>If the property is currently notifying listeners.</returns>
private bool IsNotifying(TProperty property)
=> setRecords.TryGetValue(property, out var value) && value.Notifying;
/// <summary>
/// Add a pending assignment for the property.
/// </summary>
/// <param name="property">The property.</param>
/// <param name="value">The value to assign.</param>
private void AddPendingSet(TProperty property, TSetRecord value)
{
Contract.Requires<InvalidOperationException>(IsNotifying(property));
setRecords.GetOrCreateValue(property).PendingValues.Enqueue(value);
}
/// <summary>
/// Checks if there are any pending assignments for the property.
/// </summary>
/// <param name="property">The property to check.</param>
/// <returns>If the property has any pending assignments.</returns>
private bool HasPendingSet(TProperty property)
{
return setRecords.TryGetValue(property, out var status) && status.PendingValues.Count != 0;
}
/// <summary>
/// Gets the first pending assignment for the property.
/// </summary>
/// <param name="property">The property to check.</param>
/// <returns>The first pending assignment for the property.</returns>
private TSetRecord GetFirstPendingSet(TProperty property)
{
return setRecords.GetOrCreateValue(property).PendingValues.Dequeue();
}
public delegate bool SetterDelegate<TValue>(TSetRecord record, ref TValue backing, Action<Action> notifyCallback);
/// <summary>
/// Set the property and notify listeners while ensuring we don't get into a stack overflow as happens with #855 and #824
/// </summary>
/// <param name="property">The property to set.</param>
/// <param name="backing">The backing field for the property</param>
/// <param name="setterCallback">
/// A callback that actually sets the property.
/// The first parameter is the value to set, and the second is a wrapper that takes a callback that sends the property-changed notification.
/// </param>
/// <param name="value">The value to try to set.</param>
public bool SetAndNotify<TValue>(
TProperty property,
ref TValue backing,
SetterDelegate<TValue> setterCallback,
TSetRecord value)
{
Contract.Requires<ArgumentNullException>(setterCallback != null);
if (!IsNotifying(property))
{
bool updated = false;
if (!object.Equals(value, backing))
{
updated = setterCallback(value, ref backing, notification =>
{
using (MarkNotifying(property))
{
notification();
}
});
}
while (HasPendingSet(property))
{
updated |= setterCallback(GetFirstPendingSet(property), ref backing, notification =>
{
using (MarkNotifying(property))
{
notification();
}
});
}
return updated;
}
else if(!object.Equals(value, backing))
{
AddPendingSet(property, value);
}
return false;
}
}
}

20
src/Avalonia.Controls/AppBuilderBase.cs

@ -14,6 +14,8 @@ namespace Avalonia.Controls
/// <typeparam name="TAppBuilder">The type of the AppBuilder class itself.</typeparam>
public abstract class AppBuilderBase<TAppBuilder> where TAppBuilder : AppBuilderBase<TAppBuilder>, new()
{
private static bool s_setupWasAlreadyCalled;
/// <summary>
/// Gets or sets the <see cref="IRuntimePlatform"/> instance.
/// </summary>
@ -207,6 +209,17 @@ namespace Avalonia.Controls
public TAppBuilder UseAvaloniaModules() => AfterSetup(builder => SetupAvaloniaModules());
private bool CheckSetup { get; set; } = true;
/// <summary>
/// Set this AppBuilder to ignore the setup check. Used for testing purposes.
/// </summary>
internal TAppBuilder IgnoreSetupCheck()
{
CheckSetup = false;
return Self;
}
private void SetupAvaloniaModules()
{
var moduleInitializers = from assembly in AvaloniaLocator.Current.GetService<IRuntimePlatform>().GetLoadedAssemblies()
@ -252,6 +265,13 @@ namespace Avalonia.Controls
throw new InvalidOperationException("No rendering system configured.");
}
if (s_setupWasAlreadyCalled && CheckSetup)
{
throw new InvalidOperationException("Setup was already called on one of AppBuilder instances");
}
s_setupWasAlreadyCalled = true;
Instance.RegisterServices();
RuntimePlatformServicesInitializer();
WindowingSubsystemInitializer();

9
src/Avalonia.Controls/Application.cs

@ -175,6 +175,15 @@ namespace Avalonia
closable.Closed += (s, e) => source.Cancel();
Dispatcher.UIThread.MainLoop(source.Token);
}
/// <summary>
/// Runs the application's main loop until the <see cref="CancellationToken"/> is cancelled.
/// </summary>
/// <param name="token">The token to track</param>
public void Run(CancellationToken token)
{
Dispatcher.UIThread.MainLoop(token);
}
/// <summary>
/// Exits the application

4
src/Avalonia.Controls/Avalonia.Controls.csproj

@ -11,7 +11,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Debug\Avalonia.Controls.XML</DocumentationFile>
<DocumentationFile>bin\Debug\Avalonia.Controls.xml</DocumentationFile>
<NoWarn>CS1591;CS0067</NoWarn>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
@ -21,7 +21,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Release\Avalonia.Controls.XML</DocumentationFile>
<DocumentationFile>bin\Release\Avalonia.Controls.xml</DocumentationFile>
<NoWarn>CS1591</NoWarn>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>

2
src/Avalonia.Controls/Calendar/Calendar.cs

@ -549,7 +549,7 @@ namespace Avalonia.Controls
}
else
{
if (addedDate.HasValue && !(SelectedDates.Count > 0 && SelectedDates[0] == addedDate.Value))
if (!(SelectedDates.Count > 0 && SelectedDates[0] == addedDate.Value))
{
foreach (DateTime item in SelectedDates)
{

1
src/Avalonia.Controls/ContentControl.cs

@ -51,6 +51,7 @@ namespace Avalonia.Controls
/// Gets or sets the content to display.
/// </summary>
[Content]
[DependsOn(nameof(ContentTemplate))]
public object Content
{
get { return GetValue(ContentProperty); }

15
src/Avalonia.Controls/Control.cs

@ -487,11 +487,6 @@ namespace Avalonia.Controls
void ILogical.NotifyResourcesChanged(ResourcesChangedEventArgs e)
{
ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs());
foreach (var child in LogicalChildren)
{
child.NotifyResourcesChanged(e);
}
}
/// <inheritdoc/>
@ -536,6 +531,15 @@ namespace Avalonia.Controls
}
_parent = (IControl)parent;
if (old != null)
{
old.ResourcesChanged -= ThisResourcesChanged;
}
if (_parent != null)
{
_parent.ResourcesChanged += ThisResourcesChanged;
}
((ILogical)this).NotifyResourcesChanged(new ResourcesChangedEventArgs());
if (_parent is IStyleRoot || _parent?.IsAttachedToLogicalTree == true || this is IStyleRoot)
@ -621,7 +625,6 @@ namespace Avalonia.Controls
Contract.Requires<ArgumentNullException>(property != null);
Contract.Requires<ArgumentNullException>(selector != null);
Contract.Requires<ArgumentNullException>(className != null);
Contract.Requires<ArgumentNullException>(property != null);
if (string.IsNullOrWhiteSpace(className))
{

33
src/Avalonia.Controls/DropDown.cs

@ -96,6 +96,16 @@ namespace Avalonia.Controls
this.UpdateSelectionBoxItem(this.SelectedItem);
}
protected override void OnGotFocus(GotFocusEventArgs e)
{
base.OnGotFocus(e);
if (!e.Handled && e.NavigationMethod == NavigationMethod.Directional)
{
e.Handled = UpdateSelectionFromEventSource(e.Source);
}
}
/// <inheritdoc/>
protected override void OnKeyDown(KeyEventArgs e)
{
@ -104,7 +114,7 @@ namespace Avalonia.Controls
if (!e.Handled)
{
if (e.Key == Key.F4 ||
(e.Key == Key.Down && ((e.Modifiers & InputModifiers.Alt) != 0)))
((e.Key == Key.Down || e.Key == Key.Up) && ((e.Modifiers & InputModifiers.Alt) != 0)))
{
IsDropDownOpen = !IsDropDownOpen;
e.Handled = true;
@ -114,6 +124,27 @@ namespace Avalonia.Controls
IsDropDownOpen = false;
e.Handled = true;
}
if (!IsDropDownOpen)
{
if (e.Key == Key.Down)
{
if (SelectedIndex == -1)
SelectedIndex = 0;
if (++SelectedIndex >= ItemCount)
SelectedIndex = 0;
e.Handled = true;
}
else if (e.Key == Key.Up)
{
if (--SelectedIndex < 0)
SelectedIndex = ItemCount - 1;
e.Handled = true;
}
}
}
}

4
src/Avalonia.Controls/IPanel.cs

@ -9,8 +9,8 @@ namespace Avalonia.Controls
public interface IPanel : IControl
{
/// <summary>
/// Gets or sets the children of the <see cref="Panel"/>.
/// Gets the children of the <see cref="Panel"/>.
/// </summary>
Controls Children { get; set; }
Controls Children { get; }
}
}

11
src/Avalonia.Controls/ItemsControl.cs

@ -11,6 +11,7 @@ using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Controls.Utils;
using Avalonia.Input;
using Avalonia.LogicalTree;
using Avalonia.Metadata;
@ -106,6 +107,12 @@ namespace Avalonia.Controls
set { SetAndRaise(ItemsProperty, ref _items, value); }
}
public int ItemCount
{
get;
private set;
}
/// <summary>
/// Gets or sets the panel used to display the items.
/// </summary>
@ -352,6 +359,10 @@ namespace Avalonia.Controls
RemoveControlItemsFromLogicalChildren(e.OldItems);
break;
}
int? count = (Items as IList)?.Count;
if (count != null)
ItemCount = (int)count;
var collection = sender as ICollection;
PseudoClasses.Set(":empty", collection == null || collection.Count == 0);

43
src/Avalonia.Controls/Panel.cs

@ -25,8 +25,6 @@ namespace Avalonia.Controls
public static readonly StyledProperty<IBrush> BackgroundProperty =
Border.BackgroundProperty.AddOwner<Panel>();
private readonly Controls _children = new Controls();
/// <summary>
/// Initializes static members of the <see cref="Panel"/> class.
/// </summary>
@ -40,38 +38,14 @@ namespace Avalonia.Controls
/// </summary>
public Panel()
{
_children.CollectionChanged += ChildrenChanged;
Children.CollectionChanged += ChildrenChanged;
}
/// <summary>
/// Gets or sets the children of the <see cref="Panel"/>.
/// Gets the children of the <see cref="Panel"/>.
/// </summary>
/// <remarks>
/// Even though this property can be set, the setter is only intended for use in object
/// initializers. Assigning to this property does not change the underlying collection,
/// it simply clears the existing collection and adds the contents of the assigned
/// collection.
/// </remarks>
[Content]
public Controls Children
{
get
{
return _children;
}
set
{
Contract.Requires<ArgumentNullException>(value != null);
if (_children != value)
{
VisualChildren.Clear();
_children.Clear();
_children.AddRange(value);
}
}
}
public Controls Children { get; } = new Controls();
/// <summary>
/// Gets or Sets Panel background brush.
@ -115,6 +89,11 @@ namespace Avalonia.Controls
VisualChildren.AddRange(e.NewItems.OfType<Visual>());
break;
case NotifyCollectionChangedAction.Move:
LogicalChildren.MoveRange(e.OldStartingIndex, e.OldItems.Count, e.NewStartingIndex);
VisualChildren.MoveRange(e.OldStartingIndex, e.OldItems.Count, e.NewStartingIndex);
break;
case NotifyCollectionChangedAction.Remove:
controls = e.OldItems.OfType<Control>().ToList();
LogicalChildren.RemoveAll(controls);
@ -132,11 +111,7 @@ namespace Avalonia.Controls
break;
case NotifyCollectionChangedAction.Reset:
controls = e.OldItems.OfType<Control>().ToList();
LogicalChildren.Clear();
VisualChildren.Clear();
VisualChildren.AddRange(_children);
break;
throw new NotSupportedException();
}
InvalidateMeasure();

15
src/Avalonia.Controls/Presenters/ContentPresenter.cs

@ -8,6 +8,7 @@ using Avalonia.Controls.Templates;
using Avalonia.Layout;
using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.Metadata;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Presenters
@ -139,6 +140,7 @@ namespace Avalonia.Controls.Presenters
/// <summary>
/// Gets or sets the content to be displayed by the presenter.
/// </summary>
[DependsOn(nameof(ContentTemplate))]
public object Content
{
get { return GetValue(ContentProperty); }
@ -255,18 +257,9 @@ namespace Avalonia.Controls.Presenters
LogicalChildren.Remove(oldChild);
}
if (newChild.Parent == null)
if (newChild.Parent == null && TemplatedParent == null)
{
var templatedLogicalParent = TemplatedParent as ILogical;
if (templatedLogicalParent != null)
{
((ISetLogicalParent)newChild).SetParent(templatedLogicalParent);
}
else
{
LogicalChildren.Add(newChild);
}
LogicalChildren.Add(newChild);
}
VisualChildren.Add(newChild);

31
src/Avalonia.Controls/Primitives/PopupRoot.cs

@ -8,6 +8,7 @@ using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Styling;
using Avalonia.VisualTree;
using JetBrains.Annotations;
@ -16,7 +17,7 @@ namespace Avalonia.Controls.Primitives
/// <summary>
/// The root window of a <see cref="Popup"/>.
/// </summary>
public class PopupRoot : WindowBase, IInteractive, IHostedVisualTreeRoot, IDisposable
public class PopupRoot : WindowBase, IInteractive, IHostedVisualTreeRoot, IDisposable, IStyleHost
{
private IDisposable _presenterSubscription;
@ -66,6 +67,11 @@ namespace Avalonia.Controls.Primitives
/// </summary>
IVisual IHostedVisualTreeRoot.Host => Parent;
/// <summary>
/// Gets the styling parent of the popup root.
/// </summary>
IStyleHost IStyleHost.StylingParent => Parent;
/// <inheritdoc/>
public void Dispose() => PlatformImpl?.Dispose();
@ -90,20 +96,23 @@ namespace Avalonia.Controls.Primitives
private void SetTemplatedParentAndApplyChildTemplates(IControl control)
{
var templatedParent = Parent.TemplatedParent;
if (control.TemplatedParent == null)
if (control != null)
{
control.SetValue(TemplatedParentProperty, templatedParent);
}
var templatedParent = Parent.TemplatedParent;
control.ApplyTemplate();
if (control.TemplatedParent == null)
{
control.SetValue(TemplatedParentProperty, templatedParent);
}
if (!(control is IPresenter) && control.TemplatedParent == templatedParent)
{
foreach (IControl child in control.GetVisualChildren())
control.ApplyTemplate();
if (!(control is IPresenter) && control.TemplatedParent == templatedParent)
{
SetTemplatedParentAndApplyChildTemplates(child);
foreach (IControl child in control.GetVisualChildren())
{
SetTemplatedParentAndApplyChildTemplates(child);
}
}
}
}

4
src/Avalonia.Controls/Primitives/RangeBase.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 Avalonia.Data;
using Avalonia.Utilities;
namespace Avalonia.Controls.Primitives
@ -36,7 +37,8 @@ namespace Avalonia.Controls.Primitives
AvaloniaProperty.RegisterDirect<RangeBase, double>(
nameof(Value),
o => o.Value,
(o, v) => o.Value = v);
(o, v) => o.Value = v,
defaultBindingMode: BindingMode.TwoWay);
/// <summary>
/// Defines the <see cref="SmallChange"/> property.

72
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@ -151,15 +151,23 @@ namespace Avalonia.Controls.Primitives
{
if (_updateCount == 0)
{
var old = SelectedIndex;
var effective = (value >= 0 && value < Items?.Cast<object>().Count()) ? value : -1;
if (old != effective)
SetAndRaise(SelectedIndexProperty, ref _selectedIndex, (int val, ref int backing, Action<Action> notifyWrapper) =>
{
_selectedIndex = effective;
RaisePropertyChanged(SelectedIndexProperty, old, effective, BindingPriority.LocalValue);
SelectedItem = ElementAt(Items, effective);
}
var old = backing;
var effective = (val >= 0 && val < Items?.Cast<object>().Count()) ? val : -1;
if (old != effective)
{
backing = effective;
notifyWrapper(() =>
RaisePropertyChanged(
SelectedIndexProperty,
old,
effective,
BindingPriority.LocalValue));
SelectedItem = ElementAt(Items, effective);
}
}, value);
}
else
{
@ -183,31 +191,41 @@ namespace Avalonia.Controls.Primitives
{
if (_updateCount == 0)
{
var old = SelectedItem;
var index = IndexOf(Items, value);
var effective = index != -1 ? value : null;
if (!object.Equals(effective, old))
SetAndRaise(SelectedItemProperty, ref _selectedItem, (object val, ref object backing, Action<Action> notifyWrapper) =>
{
_selectedItem = effective;
RaisePropertyChanged(SelectedItemProperty, old, effective, BindingPriority.LocalValue);
SelectedIndex = index;
var old = backing;
var index = IndexOf(Items, val);
var effective = index != -1 ? val : null;
if (effective != null)
if (!object.Equals(effective, old))
{
if (SelectedItems.Count != 1 || SelectedItems[0] != effective)
backing = effective;
notifyWrapper(() =>
RaisePropertyChanged(
SelectedItemProperty,
old,
effective,
BindingPriority.LocalValue));
SelectedIndex = index;
if (effective != null)
{
if (SelectedItems.Count != 1 || SelectedItems[0] != effective)
{
_syncingSelectedItems = true;
SelectedItems.Clear();
SelectedItems.Add(effective);
_syncingSelectedItems = false;
}
}
else if (SelectedItems.Count > 0)
{
_syncingSelectedItems = true;
SelectedItems.Clear();
SelectedItems.Add(effective);
_syncingSelectedItems = false;
}
}
else if (SelectedItems.Count > 0)
{
SelectedItems.Clear();
}
}
}, value);
}
else
{
@ -297,7 +315,7 @@ namespace Avalonia.Controls.Primitives
.OfType<IControl>()
.FirstOrDefault(x => x.LogicalParent == this && ItemContainerGenerator?.IndexFromContainer(x) != -1);
return item as IControl;
return item;
}
/// <inheritdoc/>

12
src/Avalonia.Controls/Primitives/Track.cs

@ -154,7 +154,11 @@ namespace Avalonia.Controls.Primitives
if (increaseButton != null)
{
increaseButton.Arrange(new Rect(firstWidth + thumbWidth, 0, remaining - firstWidth, finalSize.Height));
increaseButton.Arrange(new Rect(
firstWidth + thumbWidth,
0,
Math.Max(0, remaining - firstWidth),
finalSize.Height));
}
}
else
@ -185,7 +189,11 @@ namespace Avalonia.Controls.Primitives
if (increaseButton != null)
{
increaseButton.Arrange(new Rect(0, firstHeight + thumbHeight, finalSize.Width, Math.Max(remaining - firstHeight, 0)));
increaseButton.Arrange(new Rect(
0,
firstHeight + thumbHeight,
finalSize.Width,
Math.Max(remaining - firstHeight, 0)));
}
}

3
src/Avalonia.Controls/ProgressBar.cs

@ -28,9 +28,6 @@ namespace Avalonia.Controls
{
ValueProperty.Changed.AddClassHandler<ProgressBar>(x => x.ValueChanged);
HorizontalAlignmentProperty.OverrideDefaultValue<ProgressBar>(HorizontalAlignment.Left);
VerticalAlignmentProperty.OverrideDefaultValue<ProgressBar>(VerticalAlignment.Top);
IsIndeterminateProperty.Changed.AddClassHandler<ProgressBar>(
(p, e) => { if (p._indicator != null) p.UpdateIsIndeterminate((bool)e.NewValue); });
OrientationProperty.Changed.AddClassHandler<ProgressBar>(

8
src/Avalonia.Controls/TextBox.cs

@ -178,6 +178,10 @@ namespace Avalonia.Controls
{
value = CoerceCaretIndex(value);
SetAndRaise(SelectionStartProperty, ref _selectionStart, value);
if (SelectionStart == SelectionEnd)
{
CaretIndex = SelectionStart;
}
}
}
@ -192,6 +196,10 @@ namespace Avalonia.Controls
{
value = CoerceCaretIndex(value);
SetAndRaise(SelectionEndProperty, ref _selectionEnd, value);
if (SelectionStart == SelectionEnd)
{
CaretIndex = SelectionEnd;
}
}
}

5
src/Avalonia.Controls/TreeView.cs

@ -176,10 +176,7 @@ namespace Avalonia.Controls
SelectedItem = item;
if (SelectedItem != null)
{
MarkContainerSelected(container, true);
}
MarkContainerSelected(container, true);
}
}

8
src/Avalonia.Controls/VirtualizingStackPanel.cs

@ -134,12 +134,14 @@ namespace Avalonia.Controls
protected override IInputElement GetControlInDirection(NavigationDirection direction, IControl from)
{
if (from == null)
return null;
var logicalScrollable = Parent as ILogicalScrollable;
var fromControl = from as IControl;
if (logicalScrollable?.IsLogicalScrollEnabled == true && fromControl != null)
if (logicalScrollable?.IsLogicalScrollEnabled == true)
{
return logicalScrollable.GetControlInDirection(direction, fromControl);
return logicalScrollable.GetControlInDirection(direction, from);
}
else
{

4
src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj

@ -11,7 +11,7 @@
<DefineConstants>TRACE;DEBUG</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Debug\Avalonia.Diagnostics.XML</DocumentationFile>
<DocumentationFile>bin\Debug\Avalonia.Diagnostics.xml</DocumentationFile>
<NoWarn>CS1591</NoWarn>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
@ -21,7 +21,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Release\Avalonia.Diagnostics.XML</DocumentationFile>
<DocumentationFile>bin\Release\Avalonia.Diagnostics.xml</DocumentationFile>
<NoWarn>CS1591</NoWarn>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>

6
src/Avalonia.Diagnostics/Views/TreePage.xaml.cs

@ -27,6 +27,12 @@ namespace Avalonia.Diagnostics.Views
if (layer != null)
{
if (_adorner != null)
{
((Panel)_adorner.Parent).Children.Remove(_adorner);
_adorner = null;
}
_adorner = new Rectangle
{
Fill = new SolidColorBrush(0x80a0c5e8),

2
src/Avalonia.DotNetCoreRuntime/Avalonia.DotNetCoreRuntime.csproj

@ -5,7 +5,7 @@
<DefineConstants>$(DefineConstants);DOTNETCORE</DefineConstants>
</PropertyGroup>
<PropertyGroup>
<DocumentationFile>bin\$(Configuration)\Avalonia.DotNetCoreRuntime.XML</DocumentationFile>
<DocumentationFile>bin\$(Configuration)\Avalonia.DotNetCoreRuntime.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Shared\SharedAssemblyInfo.cs">

4
src/Avalonia.Input/Avalonia.Input.csproj

@ -11,7 +11,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Debug\Avalonia.Input.XML</DocumentationFile>
<DocumentationFile>bin\Debug\Avalonia.Input.xml</DocumentationFile>
<NoWarn>CS1591</NoWarn>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
@ -21,7 +21,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Release\Avalonia.Input.XML</DocumentationFile>
<DocumentationFile>bin\Release\Avalonia.Input.xml</DocumentationFile>
<NoWarn>CS1591</NoWarn>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>

17
src/Avalonia.Input/KeyGesture.cs

@ -111,6 +111,21 @@ namespace Avalonia.Input
return string.Join(" + ", parts);
}
public bool Matches(KeyEventArgs keyEvent) => keyEvent.Key == Key && keyEvent.Modifiers == Modifiers;
public bool Matches(KeyEventArgs keyEvent) => ResolveNumPadOperationKey(keyEvent.Key) == Key && keyEvent.Modifiers == Modifiers;
private Key ResolveNumPadOperationKey(Key key)
{
switch (key)
{
case Key.Add:
return Key.OemPlus;
case Key.Subtract:
return Key.OemMinus;
case Key.Decimal:
return Key.OemPeriod;
default:
return key;
}
}
}
}

4
src/Avalonia.Interactivity/Avalonia.Interactivity.csproj

@ -11,7 +11,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Debug\Avalonia.Interactivity.XML</DocumentationFile>
<DocumentationFile>bin\Debug\Avalonia.Interactivity.xml</DocumentationFile>
<NoWarn>CS1591</NoWarn>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
@ -21,7 +21,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Release\Avalonia.Interactivity.XML</DocumentationFile>
<DocumentationFile>bin\Release\Avalonia.Interactivity.xml</DocumentationFile>
<NoWarn>CS1591</NoWarn>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>

4
src/Avalonia.Layout/Avalonia.Layout.csproj

@ -11,7 +11,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Debug\Avalonia.Layout.XML</DocumentationFile>
<DocumentationFile>bin\Debug\Avalonia.Layout.xml</DocumentationFile>
<NoWarn>CS1591</NoWarn>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
@ -21,7 +21,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Release\Avalonia.Layout.XML</DocumentationFile>
<DocumentationFile>bin\Release\Avalonia.Layout.xml</DocumentationFile>
<NoWarn>CS1591</NoWarn>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>

4
src/Avalonia.Logging.Serilog/Avalonia.Logging.Serilog.csproj

@ -11,7 +11,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Debug\Avalonia.Logging.Serilog.XML</DocumentationFile>
<DocumentationFile>bin\Debug\Avalonia.Logging.Serilog.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DebugType>pdbonly</DebugType>
@ -20,7 +20,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Release\Avalonia.Logging.Serilog.XML</DocumentationFile>
<DocumentationFile>bin\Release\Avalonia.Logging.Serilog.xml</DocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>

4
src/Avalonia.Styling/Avalonia.Styling.csproj

@ -12,7 +12,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Debug\Avalonia.Styling.XML</DocumentationFile>
<DocumentationFile>bin\Debug\Avalonia.Styling.xml</DocumentationFile>
<NoWarn>CS1591</NoWarn>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
@ -22,7 +22,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Release\Avalonia.Styling.XML</DocumentationFile>
<DocumentationFile>bin\Release\Avalonia.Styling.xml</DocumentationFile>
<NoWarn>CS1591</NoWarn>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>

4
src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj

@ -11,7 +11,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Debug\Avalonia.Themes.Default.XML</DocumentationFile>
<DocumentationFile>bin\Debug\Avalonia.Themes.Default.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DebugType>pdbonly</DebugType>
@ -20,7 +20,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Release\Avalonia.Themes.Default.XML</DocumentationFile>
<DocumentationFile>bin\Release\Avalonia.Themes.Default.xml</DocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>

2
src/Avalonia.Themes.Default/Button.xaml

@ -13,8 +13,8 @@
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Padding="{TemplateBinding Padding}"
TextBlock.Foreground="{TemplateBinding Foreground}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"

2
src/Avalonia.Themes.Default/CalendarButton.xaml

@ -29,8 +29,8 @@
<!--Focusable="False"-->
<ContentControl Name="Content"
Foreground="#FF333333"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
FontSize="{TemplateBinding FontSize}"

2
src/Avalonia.Themes.Default/CalendarDayButton.xaml

@ -30,8 +30,8 @@
Fill="{TemplateBinding Background}"/>
<ContentControl Name="Content"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
FontSize="{TemplateBinding FontSize}"

2
src/Avalonia.Themes.Default/CheckBox.xaml

@ -31,8 +31,8 @@
</Panel>
</Border>
<ContentPresenter Name="PART_ContentPresenter"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Margin="4,0,0,0"
VerticalAlignment="Center"
Grid.Column="1"/>

2
src/Avalonia.Themes.Default/ContentControl.xaml

@ -5,8 +5,8 @@
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Padding="{TemplateBinding Padding}"/>
</ControlTemplate>
</Setter>

2
src/Avalonia.Themes.Default/DropDownItem.xaml

@ -10,8 +10,8 @@
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Padding="{TemplateBinding Padding}"/>

2
src/Avalonia.Themes.Default/EmbeddableControlRoot.xaml

@ -7,8 +7,8 @@
<Border Background="{TemplateBinding Background}">
<AdornerDecorator>
<ContentPresenter Name="PART_ContentPresenter"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Margin="{TemplateBinding Padding}"/>
</AdornerDecorator>
</Border>

8
src/Avalonia.Themes.Default/Expander.xaml

@ -16,8 +16,8 @@
<ContentPresenter Name="PART_ContentPresenter"
Grid.Row="1"
IsVisible="{TemplateBinding IsExpanded}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" />
</Grid>
@ -34,8 +34,8 @@
<ContentPresenter Name="PART_ContentPresenter"
Grid.Row="0"
IsVisible="{TemplateBinding IsExpanded}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" />
</Grid>
@ -52,8 +52,8 @@
<ContentPresenter Name="PART_ContentPresenter"
Grid.Column="1"
IsVisible="{TemplateBinding IsExpanded}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" />
</Grid>
@ -70,8 +70,8 @@
<ContentPresenter Name="PART_ContentPresenter"
Grid.Column="0"
IsVisible="{TemplateBinding IsExpanded}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" />
</Grid>

2
src/Avalonia.Themes.Default/LayoutTransformControl.xaml

@ -5,8 +5,8 @@
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Padding="{TemplateBinding Padding}"/>
</ControlTemplate>
</Setter>

2
src/Avalonia.Themes.Default/ListBoxItem.xaml

@ -7,8 +7,8 @@
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Padding="{TemplateBinding Padding}"/>
</ControlTemplate>
</Setter>

2
src/Avalonia.Themes.Default/PopupRoot.xaml

@ -4,8 +4,8 @@
<ControlTemplate>
<ContentPresenter Name="PART_ContentPresenter"
Background="{TemplateBinding Background}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Padding="{TemplateBinding Padding}"/>
</ControlTemplate>
</Setter>

2
src/Avalonia.Themes.Default/RadioButton.xaml

@ -29,8 +29,8 @@
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
<ContentPresenter Name="PART_ContentPresenter"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Margin="4,0,0,0"
VerticalAlignment="Center"
Grid.Column="1"/>

2
src/Avalonia.Themes.Default/RepeatButton.xaml

@ -20,8 +20,8 @@
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Padding="{TemplateBinding Padding}"
TextBlock.Foreground="{TemplateBinding Foreground}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"

2
src/Avalonia.Themes.Default/TabStripItem.xaml

@ -9,8 +9,8 @@
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Padding="{TemplateBinding Padding}"/>
</ControlTemplate>
</Setter>

2
src/Avalonia.Themes.Default/ToggleButton.xaml

@ -13,8 +13,8 @@
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Padding="{TemplateBinding Padding}"
TextBlock.Foreground="{TemplateBinding Foreground}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"

2
src/Avalonia.Themes.Default/ToolTip.xaml

@ -9,8 +9,8 @@
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Padding="{TemplateBinding Padding}"/>
</ControlTemplate>
</Setter>

2
src/Avalonia.Themes.Default/Window.xaml

@ -7,8 +7,8 @@
<Border Background="{TemplateBinding Background}">
<AdornerDecorator>
<ContentPresenter Name="PART_ContentPresenter"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Margin="{TemplateBinding Padding}"/>
</AdornerDecorator>
</Border>

2
src/Avalonia.Visuals/Media/PathMarkupParser.cs

@ -320,7 +320,7 @@ namespace Avalonia.Media
if (c == 'E')
{
readSign = false;
readExponent = c == 'E';
readExponent = true;
}
}
else

50
src/Avalonia.Visuals/Rendering/DeferredRenderer.cs

@ -25,11 +25,9 @@ namespace Avalonia.Rendering
private readonly IRenderLoop _renderLoop;
private readonly IVisual _root;
private readonly ISceneBuilder _sceneBuilder;
private readonly RenderLayers _layers;
private bool _running;
private Scene _scene;
private IRenderTarget _renderTarget;
private DirtyVisuals _dirty;
private IRenderTargetBitmapImpl _overlay;
private bool _updateQueued;
@ -56,7 +54,7 @@ namespace Avalonia.Rendering
_dispatcher = dispatcher ?? Dispatcher.UIThread;
_root = root;
_sceneBuilder = sceneBuilder ?? new SceneBuilder();
_layers = new RenderLayers();
Layers = new RenderLayers();
_renderLoop = renderLoop;
}
@ -78,9 +76,9 @@ namespace Avalonia.Rendering
Contract.Requires<ArgumentNullException>(renderTarget != null);
_root = root;
_renderTarget = renderTarget;
RenderTarget = renderTarget;
_sceneBuilder = sceneBuilder ?? new SceneBuilder();
_layers = new RenderLayers();
Layers = new RenderLayers();
}
/// <inheritdoc/>
@ -94,6 +92,16 @@ namespace Avalonia.Rendering
/// </summary>
public string DebugFramesPath { get; set; }
/// <summary>
/// Gets the render layers.
/// </summary>
internal RenderLayers Layers { get; }
/// <summary>
/// Gets the current render target.
/// </summary>
internal IRenderTarget RenderTarget { get; private set; }
/// <inheritdoc/>
public void AddDirty(IVisual visual)
{
@ -173,9 +181,9 @@ namespace Avalonia.Rendering
bool renderOverlay = DrawDirtyRects || DrawFps;
bool composite = false;
if (_renderTarget == null)
if (RenderTarget == null)
{
_renderTarget = ((IRenderRoot)_root).CreateRenderTarget();
RenderTarget = ((IRenderRoot)_root).CreateRenderTarget();
}
if (renderOverlay)
@ -191,8 +199,8 @@ namespace Avalonia.Rendering
if (scene.Generation != _lastSceneId)
{
context = _renderTarget.CreateDrawingContext(this);
_layers.Update(scene, context);
context = RenderTarget.CreateDrawingContext(this);
Layers.Update(scene, context);
RenderToLayers(scene);
@ -208,13 +216,13 @@ namespace Avalonia.Rendering
if (renderOverlay)
{
context = context ?? _renderTarget.CreateDrawingContext(this);
context = context ?? RenderTarget.CreateDrawingContext(this);
RenderOverlay(scene, context);
RenderComposite(scene, context);
}
else if (composite)
{
context = context ?? _renderTarget.CreateDrawingContext(this);
context = context ?? RenderTarget.CreateDrawingContext(this);
RenderComposite(scene, context);
}
@ -224,8 +232,8 @@ namespace Avalonia.Rendering
catch (RenderTargetCorruptedException ex)
{
Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex);
_renderTarget?.Dispose();
_renderTarget = null;
RenderTarget?.Dispose();
RenderTarget = null;
}
}
@ -235,9 +243,11 @@ namespace Avalonia.Rendering
{
clipBounds = node.ClipBounds.Intersect(clipBounds);
if (!clipBounds.IsEmpty)
if (!clipBounds.IsEmpty && node.Opacity > 0)
{
node.BeginRender(context);
var isLayerRoot = node.Visual == layer;
node.BeginRender(context, isLayerRoot);
foreach (var operation in node.DrawOperations)
{
@ -251,7 +261,7 @@ namespace Avalonia.Rendering
Render(context, (VisualNode)child, layer, clipBounds);
}
node.EndRender(context);
node.EndRender(context, isLayerRoot);
}
}
}
@ -262,7 +272,7 @@ namespace Avalonia.Rendering
{
foreach (var layer in scene.Layers)
{
var renderTarget = _layers[layer.LayerRoot].Bitmap;
var renderTarget = Layers[layer.LayerRoot].Bitmap;
var node = (VisualNode)scene.FindNode(layer.LayerRoot);
if (node != null)
@ -322,7 +332,7 @@ namespace Avalonia.Rendering
foreach (var layer in scene.Layers)
{
var bitmap = _layers[layer.LayerRoot].Bitmap;
var bitmap = Layers[layer.LayerRoot].Bitmap;
var sourceRect = new Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight);
if (layer.GeometryClip != null)
@ -353,7 +363,7 @@ namespace Avalonia.Rendering
if (DrawFps)
{
RenderFps(context, clientRect, true);
RenderFps(context, clientRect, scene.Layers.Count);
}
}
@ -442,7 +452,7 @@ namespace Avalonia.Rendering
{
var index = 0;
foreach (var layer in _layers)
foreach (var layer in Layers)
{
var fileName = Path.Combine(DebugFramesPath, $"frame-{id}-layer-{index++}.png");
layer.Bitmap.Save(fileName);

2
src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs

@ -69,7 +69,7 @@ namespace Avalonia.Rendering
if (DrawFps)
{
RenderFps(context.PlatformImpl, _root.Bounds, true);
RenderFps(context.PlatformImpl, _root.Bounds, null);
}
}
}

17
src/Avalonia.Visuals/Rendering/RendererBase.cs

@ -22,15 +22,12 @@ namespace Avalonia.Rendering
};
}
protected void RenderFps(IDrawingContextImpl context, Rect clientRect, bool incrementFrameCount)
protected void RenderFps(IDrawingContextImpl context, Rect clientRect, int? layerCount)
{
var now = _stopwatch.Elapsed;
var elapsed = now - _lastFpsUpdate;
if (incrementFrameCount)
{
++_framesThisSecond;
}
++_framesThisSecond;
if (elapsed.TotalSeconds > 1)
{
@ -39,7 +36,15 @@ namespace Avalonia.Rendering
_lastFpsUpdate = now;
}
_fpsText.Text = string.Format("FPS: {0:000}", _fps);
if (layerCount.HasValue)
{
_fpsText.Text = string.Format("Layers: {0} FPS: {1:000}", layerCount, _fps);
}
else
{
_fpsText.Text = string.Format("FPS: {0:000}", _fps);
}
var size = _fpsText.Measure();
var rect = new Rect(clientRect.Right - size.Width, 0, size.Width, size.Height);

6
src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs

@ -72,13 +72,15 @@ namespace Avalonia.Rendering.SceneGraph
/// Sets up the drawing context for rendering the node's geometry.
/// </summary>
/// <param name="context">The drawing context.</param>
void BeginRender(IDrawingContextImpl context);
/// <param name="skipOpacity">Whether to skip pushing the control's opacity.</param>
void BeginRender(IDrawingContextImpl context, bool skipOpacity);
/// <summary>
/// Resets the drawing context after rendering the node's geometry.
/// </summary>
/// <param name="context">The drawing context.</param>
void EndRender(IDrawingContextImpl context);
/// <param name="skipOpacity">Whether to skip popping the control's opacity.</param>
void EndRender(IDrawingContextImpl context, bool skipOpacity);
/// <summary>
/// Hit test the geometry in this node.

17
src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs

@ -167,7 +167,6 @@ namespace Avalonia.Rendering.SceneGraph
using (context.PushPostTransform(m))
using (context.PushTransformContainer())
{
var startLayer = opacity < 1 || visual.OpacityMask != null;
var clipBounds = bounds.TransformToAABB(contextImpl.Transform).Intersect(clip);
forceRecurse = forceRecurse ||
@ -179,9 +178,11 @@ namespace Avalonia.Rendering.SceneGraph
node.ClipToBounds = clipToBounds;
node.GeometryClip = visual.Clip?.PlatformImpl;
node.Opacity = opacity;
node.OpacityMask = visual.OpacityMask;
if (startLayer)
// TODO: Check equality between node.OpacityMask and visual.OpacityMask before assigning.
node.OpacityMask = visual.OpacityMask?.ToImmutable();
if (ShouldStartLayer(visual))
{
if (node.LayerRoot != visual)
{
@ -192,7 +193,7 @@ namespace Avalonia.Rendering.SceneGraph
UpdateLayer(node, scene.Layers[node.LayerRoot]);
}
}
else if (!startLayer && node.LayerRoot == node.Visual && node.Parent != null)
else if (node.LayerRoot == node.Visual && node.Parent != null)
{
ClearLayer(scene, node);
}
@ -366,6 +367,14 @@ namespace Avalonia.Rendering.SceneGraph
}
}
private static bool ShouldStartLayer(IVisual visual)
{
var o = visual as IAvaloniaObject;
return visual.VisualChildren.Count > 0 &&
o != null &&
o.IsAnimating(Visual.OpacityProperty);
}
private static IGeometryImpl CreateLayerGeometryClip(VisualNode node)
{
IGeometryImpl result = null;

30
src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs

@ -22,6 +22,7 @@ namespace Avalonia.Rendering.SceneGraph
private List<IVisualNode> _children;
private List<IDrawOperation> _drawOperations;
private bool _drawOperationsCloned;
private Matrix transformRestore;
/// <summary>
/// Initializes a new instance of the <see cref="VisualNode"/> class.
@ -218,8 +219,10 @@ namespace Avalonia.Rendering.SceneGraph
}
/// <inheritdoc/>
public void BeginRender(IDrawingContextImpl context)
public void BeginRender(IDrawingContextImpl context, bool skipOpacity)
{
transformRestore = context.Transform;
if (ClipToBounds)
{
context.Transform = Matrix.Identity;
@ -228,24 +231,47 @@ namespace Avalonia.Rendering.SceneGraph
context.Transform = Transform;
if (Opacity != 1 && !skipOpacity)
{
context.PushOpacity(Opacity);
}
if (GeometryClip != null)
{
context.PushGeometryClip(GeometryClip);
}
if (OpacityMask != null)
{
context.PushOpacityMask(OpacityMask, ClipBounds);
}
}
/// <inheritdoc/>
public void EndRender(IDrawingContextImpl context)
public void EndRender(IDrawingContextImpl context, bool skipOpacity)
{
if (OpacityMask != null)
{
context.PopOpacityMask();
}
if (GeometryClip != null)
{
context.PopGeometryClip();
}
if (Opacity != 1 && !skipOpacity)
{
context.PopOpacity();
}
if (ClipToBounds)
{
context.Transform = Matrix.Identity;
context.PopClip();
}
context.Transform = transformRestore;
}
private Rect CalculateBounds()

13
src/Avalonia.Visuals/Visual.cs

@ -537,6 +537,19 @@ namespace Avalonia
v.SetVisualParent(null);
}
break;
case NotifyCollectionChangedAction.Replace:
foreach (Visual v in e.OldItems)
{
v.SetVisualParent(null);
}
foreach (Visual v in e.NewItems)
{
v.SetVisualParent(this);
}
break;
}
}

3
src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj

@ -22,7 +22,7 @@
<DefineConstants>NETSTANDARD1_3;PCL;NETSTANDARD</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Release\Avalonia.Markup.Xaml.XML</DocumentationFile>
<DocumentationFile>bin\Release\Avalonia.Markup.Xaml.xml</DocumentationFile>
<NoWarn>CS1591</NoWarn>
</PropertyGroup>
<ItemGroup>
@ -91,6 +91,7 @@
<Compile Include="Templates\TemplateContent.cs" />
<Compile Include="Templates\TemplateLoader.cs" />
<Compile Include="Templates\TreeDataTemplate.cs" />
<Compile Include="XamlLoadException.cs" />
<Compile Include="PortableXaml\portable.xaml.github\src\Portable.Xaml\**\*.cs" Exclude="PortableXaml\portable.xaml.github\src\Portable.Xaml\Assembly\**\*.cs" />
<Compile Remove="**\UriTypeConverter.cs" />
</ItemGroup>

14
src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoaderPortableXaml.cs

@ -127,7 +127,19 @@ namespace Avalonia.Markup.Xaml
using (var stream = assetLocator.Open(uri, baseUri))
{
return Load(stream, rootInstance, uri);
try
{
return Load(stream, rootInstance, uri);
}
catch (Exception e)
{
var uriString = uri.ToString();
if (!uri.IsAbsoluteUri)
{
uriString = new Uri(baseUri, uri).AbsoluteUri;
}
throw new XamlLoadException("Error loading xaml at " + uriString, e);
}
}
}

109
src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs

@ -66,7 +66,7 @@ namespace Avalonia.Markup.Xaml.Data
/// <summary>
/// Gets or sets the binding path.
/// </summary>
public string Path { get; set; }
public string Path { get; set; } = "";
/// <summary>
/// Gets or sets the binding priority.
@ -93,53 +93,53 @@ namespace Avalonia.Markup.Xaml.Data
bool enableDataValidation = false)
{
Contract.Requires<ArgumentNullException>(target != null);
anchor = anchor ?? DefaultAnchor?.Target;
var pathInfo = ParsePath(Path);
ValidateState(pathInfo);
enableDataValidation = enableDataValidation && Priority == BindingPriority.LocalValue;
ExpressionObserver observer;
if (pathInfo.ElementName != null || ElementName != null)
if (ElementName != null)
{
observer = CreateElementObserver(
(target as IControl) ?? (anchor as IControl),
pathInfo.ElementName ?? ElementName,
pathInfo.Path);
ElementName,
Path,
enableDataValidation);
}
else if (Source != null)
{
observer = CreateSourceObserver(Source, pathInfo.Path, enableDataValidation);
observer = CreateSourceObserver(Source, Path, enableDataValidation);
}
else if (RelativeSource == null || RelativeSource.Mode == RelativeSourceMode.DataContext)
{
observer = CreateDataContexObserver(
target,
pathInfo.Path,
Path,
targetProperty == Control.DataContextProperty,
anchor,
enableDataValidation);
}
else if (RelativeSource.Mode == RelativeSourceMode.Self)
{
observer = CreateSourceObserver(target, pathInfo.Path, enableDataValidation);
observer = CreateSourceObserver(target, Path, enableDataValidation);
}
else if (RelativeSource.Mode == RelativeSourceMode.TemplatedParent)
{
observer = CreateTemplatedParentObserver(target, pathInfo.Path);
observer = CreateTemplatedParentObserver(target, Path, enableDataValidation);
}
else if (RelativeSource.Mode == RelativeSourceMode.FindAncestor)
{
if (RelativeSource.AncestorType == null)
if (RelativeSource.Tree == TreeType.Visual && RelativeSource.AncestorType == null)
{
throw new InvalidOperationException("AncestorType must be set for RelativeSourceModel.FindAncestor.");
throw new InvalidOperationException("AncestorType must be set for RelativeSourceMode.FindAncestor when searching the visual tree.");
}
observer = CreateFindAncestorObserver(
(target as IControl) ?? (anchor as IControl),
pathInfo.Path);
RelativeSource,
Path,
enableDataValidation);
}
else
{
@ -168,53 +168,6 @@ namespace Avalonia.Markup.Xaml.Data
return new InstancedBinding(subject, Mode, Priority);
}
private static PathInfo ParsePath(string path)
{
var result = new PathInfo();
if (string.IsNullOrWhiteSpace(path) || path == ".")
{
result.Path = string.Empty;
}
else if (path.StartsWith("#"))
{
var dot = path.IndexOf('.');
if (dot != -1)
{
result.Path = path.Substring(dot + 1);
result.ElementName = path.Substring(1, dot - 1);
}
else
{
result.Path = string.Empty;
result.ElementName = path.Substring(1);
}
}
else
{
result.Path = path;
}
return result;
}
private void ValidateState(PathInfo pathInfo)
{
if (pathInfo.ElementName != null && ElementName != null)
{
throw new InvalidOperationException(
"ElementName property cannot be set when an #elementName path is provided.");
}
if ((pathInfo.ElementName != null || ElementName != null) &&
RelativeSource != null)
{
throw new InvalidOperationException(
"ElementName property cannot be set with a RelativeSource.");
}
}
private ExpressionObserver CreateDataContexObserver(
IAvaloniaObject target,
string path,
@ -256,7 +209,11 @@ namespace Avalonia.Markup.Xaml.Data
}
}
private ExpressionObserver CreateElementObserver(IControl target, string elementName, string path)
private ExpressionObserver CreateElementObserver(
IControl target,
string elementName,
string path,
bool enableDataValidation)
{
Contract.Requires<ArgumentNullException>(target != null);
@ -264,35 +221,39 @@ namespace Avalonia.Markup.Xaml.Data
var result = new ExpressionObserver(
ControlLocator.Track(target, elementName),
path,
false,
enableDataValidation,
description);
return result;
}
private ExpressionObserver CreateFindAncestorObserver(
IControl target,
string path)
RelativeSource relativeSource,
string path,
bool enableDataValidation)
{
Contract.Requires<ArgumentNullException>(target != null);
return new ExpressionObserver(
ControlLocator.Track(target, RelativeSource.AncestorType, RelativeSource.AncestorLevel -1),
path);
ControlLocator.Track(target, relativeSource.Tree, relativeSource.AncestorLevel - 1, relativeSource.AncestorType),
path,
enableDataValidation);
}
private ExpressionObserver CreateSourceObserver(
object source,
string path,
bool enabledDataValidation)
bool enableDataValidation)
{
Contract.Requires<ArgumentNullException>(source != null);
return new ExpressionObserver(source, path, enabledDataValidation);
return new ExpressionObserver(source, path, enableDataValidation);
}
private ExpressionObserver CreateTemplatedParentObserver(
IAvaloniaObject target,
string path)
string path,
bool enableDataValidation)
{
Contract.Requires<ArgumentNullException>(target != null);
@ -303,7 +264,8 @@ namespace Avalonia.Markup.Xaml.Data
var result = new ExpressionObserver(
() => target.GetValue(Control.TemplatedParentProperty),
path,
update);
update,
enableDataValidation);
return result;
}
@ -328,6 +290,7 @@ namespace Avalonia.Markup.Xaml.Data
{
public string Path { get; set; }
public string ElementName { get; set; }
public RelativeSource RelativeSource { get; set; }
}
}
}
}

2
src/Markup/Avalonia.Markup.Xaml/Data/RelativeSource.cs

@ -87,5 +87,7 @@ namespace Avalonia.Markup.Xaml.Data
/// Gets or sets a value that describes the type of relative source lookup.
/// </summary>
public RelativeSourceMode Mode { get; set; }
public TreeType Tree { get; set; } = TreeType.Visual;
}
}

153
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs

@ -29,20 +29,167 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
public override object ProvideValue(IServiceProvider serviceProvider)
{
var descriptorContext = (ITypeDescriptorContext)serviceProvider;
var pathInfo = ParsePath(Path, descriptorContext);
ValidateState(pathInfo);
return new Binding
{
Converter = Converter,
ConverterParameter = ConverterParameter,
ElementName = ElementName,
ElementName = pathInfo.ElementName ?? ElementName,
FallbackValue = FallbackValue,
Mode = Mode,
Path = Path,
Path = pathInfo.Path,
Priority = Priority,
RelativeSource = RelativeSource,
RelativeSource = pathInfo.RelativeSource ?? RelativeSource,
DefaultAnchor = new WeakReference(GetDefaultAnchor((ITypeDescriptorContext)serviceProvider))
};
}
private class PathInfo
{
public string Path { get; set; }
public string ElementName { get; set; }
public RelativeSource RelativeSource { get; set; }
}
private void ValidateState(PathInfo pathInfo)
{
if (pathInfo.ElementName != null && ElementName != null)
{
throw new InvalidOperationException(
"ElementName property cannot be set when an #elementName path is provided.");
}
if (pathInfo.RelativeSource != null && RelativeSource != null)
{
throw new InvalidOperationException(
"ElementName property cannot be set when a $self or $parent path is provided.");
}
if ((pathInfo.ElementName != null || ElementName != null) &&
(pathInfo.RelativeSource != null || RelativeSource != null))
{
throw new InvalidOperationException(
"ElementName property cannot be set with a RelativeSource.");
}
}
private static PathInfo ParsePath(string path, ITypeDescriptorContext context)
{
var result = new PathInfo();
if (string.IsNullOrWhiteSpace(path) || path == ".")
{
result.Path = string.Empty;
}
else if (path.StartsWith("#"))
{
var dot = path.IndexOf('.');
if (dot != -1)
{
result.Path = path.Substring(dot + 1);
result.ElementName = path.Substring(1, dot - 1);
}
else
{
result.Path = string.Empty;
result.ElementName = path.Substring(1);
}
}
else if (path.StartsWith("$"))
{
var relativeSource = new RelativeSource
{
Tree = TreeType.Logical
};
result.RelativeSource = relativeSource;
var dot = path.IndexOf('.');
string relativeSourceMode;
if (dot != -1)
{
result.Path = path.Substring(dot + 1);
relativeSourceMode = path.Substring(1, dot - 1);
}
else
{
result.Path = string.Empty;
relativeSourceMode = path.Substring(1);
}
if (relativeSourceMode == "self")
{
relativeSource.Mode = RelativeSourceMode.Self;
}
else if (relativeSourceMode == "parent")
{
relativeSource.Mode = RelativeSourceMode.FindAncestor;
relativeSource.AncestorLevel = 1;
}
else if (relativeSourceMode.StartsWith("parent["))
{
relativeSource.Mode = RelativeSourceMode.FindAncestor;
var parentConfigStart = relativeSourceMode.IndexOf('[');
if (!relativeSourceMode.EndsWith("]"))
{
throw new InvalidOperationException("Invalid RelativeSource binding syntax. Expected matching ']' for '['.");
}
var parentConfigParams = relativeSourceMode.Substring(parentConfigStart + 1).TrimEnd(']').Split(';');
if (parentConfigParams.Length > 2 || parentConfigParams.Length == 0)
{
throw new InvalidOperationException("Expected either 1 or 2 parameters for RelativeSource binding syntax");
}
else if (parentConfigParams.Length == 1)
{
if (int.TryParse(parentConfigParams[0], out int level))
{
relativeSource.AncestorType = null;
relativeSource.AncestorLevel = level + 1;
}
else
{
relativeSource.AncestorType = LookupAncestorType(parentConfigParams[0], context);
}
}
else
{
relativeSource.AncestorType = LookupAncestorType(parentConfigParams[0], context);
relativeSource.AncestorLevel = int.Parse(parentConfigParams[1]) + 1;
}
}
else
{
throw new InvalidOperationException($"Invalid RelativeSource binding syntax: {relativeSourceMode}");
}
}
else
{
result.Path = path;
}
return result;
}
private static Type LookupAncestorType(string ancestorTypeName, ITypeDescriptorContext context)
{
var parts = ancestorTypeName.Split(':');
if (parts.Length == 0 || parts.Length > 2)
{
throw new InvalidOperationException("Invalid type name");
}
if (parts.Length == 1)
{
return context.ResolveType(string.Empty, parts[0]);
}
else
{
return context.ResolveType(parts[0], parts[1]);
}
}
private static object GetDefaultAnchor(ITypeDescriptorContext context)
{

2
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs

@ -29,7 +29,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
ElementName = ElementName,
Mode = Mode,
RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
Path = Path,
Path = Path ?? string.Empty,
Priority = Priority,
};
}

2
src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github

@ -1 +1 @@
Subproject commit d50ae8335eb50d4b9606de6f5fa1cbbc78bfd72f
Subproject commit c0664014455392ac221a765e66f9837704339b6f

24
src/Markup/Avalonia.Markup.Xaml/XamlLoadException.cs

@ -0,0 +1,24 @@
using System;
using System.Runtime.Serialization;
namespace Avalonia.Markup.Xaml
{
public class XamlLoadException: Exception
{
public XamlLoadException()
{
}
protected XamlLoadException(SerializationInfo info, StreamingContext context): base(info, context)
{
}
public XamlLoadException(string message): base(message)
{
}
public XamlLoadException(string message, Exception innerException): base(message, innerException)
{
}
}
}

4
src/Markup/Avalonia.Markup/Avalonia.Markup.csproj

@ -11,7 +11,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Debug\Avalonia.Markup.XML</DocumentationFile>
<DocumentationFile>bin\Debug\Avalonia.Markup.xml</DocumentationFile>
<NoWarn>CS1591</NoWarn>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
@ -21,7 +21,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Release\Avalonia.Markup.XML</DocumentationFile>
<DocumentationFile>bin\Release\Avalonia.Markup.xml</DocumentationFile>
<NoWarn>CS1591</NoWarn>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>

91
src/Markup/Avalonia.Markup/ControlLocator.cs

@ -11,6 +11,21 @@ using Avalonia.VisualTree;
namespace Avalonia.Markup
{
/// <summary>
/// The type of tree via which to track a control.
/// </summary>
public enum TreeType
{
/// <summary>
/// The visual tree.
/// </summary>
Visual,
/// <summary>
/// The logical tree.
/// </summary>
Logical,
}
/// <summary>
/// Locates controls relative to other controls.
/// </summary>
@ -27,13 +42,13 @@ namespace Avalonia.Markup
{
var attached = Observable.FromEventPattern<LogicalTreeAttachmentEventArgs>(
x => relativeTo.AttachedToLogicalTree += x,
x => relativeTo.DetachedFromLogicalTree += x)
x => relativeTo.AttachedToLogicalTree -= x)
.Select(x => ((IControl)x.Sender).FindNameScope())
.StartWith(relativeTo.FindNameScope());
var detached = Observable.FromEventPattern<LogicalTreeAttachmentEventArgs>(
x => relativeTo.DetachedFromLogicalTree += x,
x => relativeTo.DetachedFromLogicalTree += x)
x => relativeTo.DetachedFromLogicalTree -= x)
.Select(x => (INameScope)null);
return attached.Merge(detached).Select(nameScope =>
@ -68,37 +83,75 @@ namespace Avalonia.Markup
/// <param name="relativeTo">
/// The control relative from which the other control should be found.
/// </param>
/// <param name="ancestorType">The type of the ancestor to find.</param>
/// <param name="tree">The tree via which to track the control.</param>
/// <param name="ancestorLevel">
/// The level of ancestor control to look for. Use 0 for the first ancestor of the
/// requested type.
/// </param>
public static IObservable<IControl> Track(IControl relativeTo, Type ancestorType, int ancestorLevel)
/// <param name="ancestorType">The type of the ancestor to find.</param>
public static IObservable<IControl> Track(IControl relativeTo, TreeType tree, int ancestorLevel, Type ancestorType = null)
{
return TrackAttachmentToTree(relativeTo, tree).Select(isAttachedToTree =>
{
if (isAttachedToTree)
{
if (tree == TreeType.Visual)
{
return relativeTo.GetVisualAncestors()
.Where(x => ancestorType?.GetTypeInfo().IsAssignableFrom(x.GetType().GetTypeInfo()) ?? true)
.ElementAtOrDefault(ancestorLevel) as IControl;
}
else
{
return relativeTo.GetLogicalAncestors()
.Where(x => ancestorType?.GetTypeInfo().IsAssignableFrom(x.GetType().GetTypeInfo()) ?? true)
.ElementAtOrDefault(ancestorLevel) as IControl;
}
}
else
{
return null;
}
});
}
private static IObservable<bool> TrackAttachmentToTree(IControl relativeTo, TreeType tree)
{
return tree == TreeType.Visual ? TrackAttachmentToVisualTree(relativeTo) : TrackAttachmentToLogicalTree(relativeTo);
}
private static IObservable<bool> TrackAttachmentToVisualTree(IControl relativeTo)
{
var attached = Observable.FromEventPattern<VisualTreeAttachmentEventArgs>(
x => relativeTo.AttachedToVisualTree += x,
x => relativeTo.DetachedFromVisualTree += x)
x => relativeTo.AttachedToVisualTree -= x)
.Select(x => true)
.StartWith(relativeTo.IsAttachedToVisualTree);
var detached = Observable.FromEventPattern<VisualTreeAttachmentEventArgs>(
x => relativeTo.DetachedFromVisualTree += x,
x => relativeTo.DetachedFromVisualTree += x)
x => relativeTo.DetachedFromVisualTree -= x)
.Select(x => false);
return attached.Merge(detached).Select(isAttachedToVisualTree =>
{
if (isAttachedToVisualTree)
{
return relativeTo.GetVisualAncestors()
.Where(x => ancestorType.GetTypeInfo().IsAssignableFrom(x.GetType().GetTypeInfo()))
.ElementAtOrDefault(ancestorLevel) as IControl;
}
else
{
return null;
}
});
var attachmentStatus = attached.Merge(detached);
return attachmentStatus;
}
private static IObservable<bool> TrackAttachmentToLogicalTree(IControl relativeTo)
{
var attached = Observable.FromEventPattern<LogicalTreeAttachmentEventArgs>(
x => relativeTo.AttachedToLogicalTree += x,
x => relativeTo.AttachedToLogicalTree -= x)
.Select(x => true)
.StartWith(relativeTo.IsAttachedToLogicalTree);
var detached = Observable.FromEventPattern<LogicalTreeAttachmentEventArgs>(
x => relativeTo.DetachedFromLogicalTree += x,
x => relativeTo.DetachedFromLogicalTree -= x)
.Select(x => false);
var attachmentStatus = attached.Merge(detached);
return attachmentStatus;
}
}
}

10
src/Markup/Avalonia.Markup/Data/Parsers/ArgumentListParser.cs

@ -51,15 +51,7 @@ namespace Avalonia.Markup.Data.Parsers
}
}
if (!r.End)
{
r.Take();
return result;
}
else
{
throw new ExpressionParseException(r.Position, "Expected ']'.");
}
throw new ExpressionParseException(r.Position, "Expected ']'.");
}
return null;

4
src/Skia/Avalonia.Skia/FormattedTextImpl.cs

@ -450,7 +450,7 @@ namespace Avalonia.Skia
{
var match = _foregroundBrushes[bi];
len = match.Key.EndIndex - index + 1;
len = match.Key.EndIndex - index;
result = match.Value;
if (len > 0 && len < length)
@ -641,7 +641,7 @@ namespace Avalonia.Skia
Length = length;
}
public int EndIndex => StartIndex + Length - 1;
public int EndIndex => StartIndex + Length;
public int Length { get; private set; }

7
src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Text;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Direct2D1.Media;
@ -68,9 +69,9 @@ namespace Avalonia.Direct2D1
for (var y = 0; y < _target.Height; y++)
{
UnmanagedMethods.CopyMemory(
_target.Address + _target.RowBytes * y,
l.Data.DataPointer + l.Stride * y,
(uint) Math.Min(l.Stride, _target.RowBytes));
(_target.Address + _target.RowBytes * y),
(l.Data.DataPointer + l.Stride * y),
(UIntPtr)Math.Min(l.Stride, _target.RowBytes));
}
}
Dispose();

1
src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs

@ -54,7 +54,6 @@ namespace Avalonia.Direct2D1.Media
_finishedCallback = finishedCallback;
_directWriteFactory = directWriteFactory;
_imagingFactory = imagingFactory;
_swapChain = swapChain;
_renderTarget.BeginDraw();
}

7
src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs

@ -3,6 +3,7 @@
using System;
using System.IO;
using System.Runtime.CompilerServices;
using Avalonia.Win32.Interop;
using SharpDX.WIC;
using APixelFormat = Avalonia.Platform.PixelFormat;
@ -74,8 +75,10 @@ namespace Avalonia.Direct2D1.Media
{
for (var row = 0; row < height; row++)
{
UnmanagedMethods.CopyMemory(new IntPtr(l.Data.DataPointer.ToInt64() + row * l.Stride),
new IntPtr(data.ToInt64() + row * stride), (uint) l.Data.Pitch);
UnmanagedMethods.CopyMemory(
(l.Data.DataPointer + row * l.Stride),
(data + row * stride),
(UIntPtr) l.Data.Pitch);
}
}
}

4
src/Windows/Avalonia.Win32/Avalonia.Win32.csproj

@ -22,7 +22,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Debug\Avalonia.Win32.XML</DocumentationFile>
<DocumentationFile>bin\Debug\Avalonia.Win32.xml</DocumentationFile>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<NoWarn>CS1591</NoWarn>
</PropertyGroup>
@ -33,7 +33,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Release\Avalonia.Win32.XML</DocumentationFile>
<DocumentationFile>bin\Release\Avalonia.Win32.xml</DocumentationFile>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<NoWarn>CS1591</NoWarn>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save