Browse Source

Merge branch 'master' into scroll

pull/1132/head
Steven Kirk 9 years ago
committed by GitHub
parent
commit
9186b63d18
  1. 6
      .gitignore
  2. 1
      .ncrunch/Avalonia.Direct2D1.RenderTests.v3.ncrunchproject
  3. 9
      .ncrunch/Avalonia.Markup.UnitTests.netcoreapp1.1.v3.ncrunchproject
  4. 1
      .ncrunch/Avalonia.Skia.RenderTests.v3.ncrunchproject
  5. 3
      .travis.yml
  6. 4
      appveyor.yml
  7. 2
      build.cake
  8. 2
      build/XUnit.props
  9. 13
      docs/guidelines/build.md
  10. 61
      packages.cake
  11. 2
      samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
  12. 2
      samples/ControlCatalog/ControlCatalog.csproj
  13. 3
      samples/ControlCatalog/MainWindow.xaml.cs
  14. 53
      samples/ControlCatalog/Pages/ToolTipPage.xaml
  15. 2
      src/Avalonia.Animation/Avalonia.Animation.csproj
  16. 2
      src/Avalonia.Base/Avalonia.Base.csproj
  17. 3
      src/Avalonia.Base/AvaloniaPropertyRegistry.cs
  18. 54
      src/Avalonia.Base/Collections/AvaloniaDictionary.cs
  19. 51
      src/Avalonia.Controls/Application.cs
  20. 2
      src/Avalonia.Controls/Avalonia.Controls.csproj
  21. 134
      src/Avalonia.Controls/Control.cs
  22. 2
      src/Avalonia.Controls/DropDown.cs
  23. 1
      src/Avalonia.Controls/IControl.cs
  24. 7
      src/Avalonia.Controls/Platform/IWindowImpl.cs
  25. 2
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  26. 26
      src/Avalonia.Controls/Primitives/Popup.cs
  27. 236
      src/Avalonia.Controls/ToolTip.cs
  28. 98
      src/Avalonia.Controls/ToolTipService.cs
  29. 24
      src/Avalonia.Controls/TopLevel.cs
  30. 24
      src/Avalonia.Controls/Window.cs
  31. 2
      src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj
  32. 2
      src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj
  33. 2
      src/Avalonia.DotNetCoreRuntime/Avalonia.DotNetCoreRuntime.csproj
  34. 94
      src/Avalonia.DotNetFrameworkRuntime/Avalonia.DotNetFrameworkRuntime.csproj
  35. 15
      src/Avalonia.DotNetFrameworkRuntime/Properties/AssemblyInfo.cs
  36. 2
      src/Avalonia.HtmlRenderer/Avalonia.HtmlRenderer.csproj
  37. 12
      src/Avalonia.HtmlRenderer/Compat/Api.cs
  38. 2
      src/Avalonia.Input/Avalonia.Input.csproj
  39. 2
      src/Avalonia.Interactivity/Avalonia.Interactivity.csproj
  40. 2
      src/Avalonia.Layout/Avalonia.Layout.csproj
  41. 2
      src/Avalonia.Logging.Serilog/Avalonia.Logging.Serilog.csproj
  42. 2
      src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj
  43. 3
      src/Avalonia.Styling/Avalonia.Styling.csproj
  44. 19
      src/Avalonia.Styling/Controls/IResourceDictionary.cs
  45. 15
      src/Avalonia.Styling/Controls/IResourceNode.cs
  46. 33
      src/Avalonia.Styling/Controls/IResourceProvider.cs
  47. 101
      src/Avalonia.Styling/Controls/ResourceDictionary.cs
  48. 65
      src/Avalonia.Styling/Controls/ResourceProviderExtensions.cs
  49. 11
      src/Avalonia.Styling/Controls/ResourcesChangedEventArgs.cs
  50. 11
      src/Avalonia.Styling/LogicalTree/ILogical.cs
  51. 2
      src/Avalonia.Styling/Properties/AssemblyInfo.cs
  52. 32
      src/Avalonia.Styling/Styling/ISetStyleParent.cs
  53. 13
      src/Avalonia.Styling/Styling/IStyle.cs
  54. 92
      src/Avalonia.Styling/Styling/Style.cs
  55. 42
      src/Avalonia.Styling/Styling/StyleExtensions.cs
  56. 90
      src/Avalonia.Styling/Styling/StyleResources.cs
  57. 149
      src/Avalonia.Styling/Styling/Styles.cs
  58. 2
      src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj
  59. 2
      src/Avalonia.Visuals/Avalonia.Visuals.csproj
  60. 18
      src/Gtk/Avalonia.Gtk/WindowImpl.cs
  61. 2
      src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj
  62. 49
      src/Gtk/Avalonia.Gtk3/Interop/Native.cs
  63. 16
      src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs
  64. 4
      src/Gtk/Avalonia.Gtk3/WindowImpl.cs
  65. 2
      src/Linux/Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj
  66. 6
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  67. 85
      src/Markup/Avalonia.Markup.Xaml/Data/DelayedBinding.cs
  68. 63
      src/Markup/Avalonia.Markup.Xaml/Data/ResourceInclude.cs
  69. 7
      src/Markup/Avalonia.Markup.Xaml/Data/StyleResourceBinding.cs
  70. 71
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs
  71. 84
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs
  72. 2
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github
  73. 41
      src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs
  74. 2
      src/Markup/Avalonia.Markup/Avalonia.Markup.csproj
  75. 2
      src/Shared/PlatformSupport/StandardRuntimePlatform.cs
  76. 2
      src/Skia/Avalonia.Skia/Avalonia.Skia.csproj
  77. 2
      src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj
  78. 2
      src/Windows/Avalonia.Win32.NetStandard/Avalonia.Win32.NetStandard.csproj
  79. 45
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  80. 15
      src/Windows/Avalonia.Win32/SystemDialogImpl.cs
  81. 22
      src/Windows/Avalonia.Win32/WindowImpl.cs
  82. 2
      tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj
  83. 2
      tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj
  84. 217
      tests/Avalonia.Controls.UnitTests/ControlTests_Resources.cs
  85. 17
      tests/Avalonia.Controls.UnitTests/TopLevelTests.cs
  86. 2
      tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj
  87. 2
      tests/Avalonia.Interactivity.UnitTests/Avalonia.Interactivity.UnitTests.csproj
  88. 2
      tests/Avalonia.Layout.UnitTests/Avalonia.Layout.UnitTests.csproj
  89. 4
      tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs
  90. 2
      tests/Avalonia.Markup.UnitTests/Avalonia.Markup.UnitTests.csproj
  91. 2
      tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj
  92. 55
      tests/Avalonia.Markup.Xaml.UnitTests/Data/ResourceIncludeTests.cs
  93. 660
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs
  94. 476
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs
  95. 20
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/TestValueConverter.cs
  96. 6
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs
  97. 12
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs
  98. 2
      tests/Avalonia.RenderTests/Avalonia.Skia.RenderTests.csproj
  99. 2
      tests/Avalonia.Styling.UnitTests/Avalonia.Styling.UnitTests.csproj
  100. 175
      tests/Avalonia.Styling.UnitTests/ResourceDictionaryTests.cs

6
.gitignore

@ -159,6 +159,12 @@ $RECYCLE.BIN/
*.userprefs
*.nugetreferenceswitcher
#################
## Rider
#################
.idea
#################
## Cake
#################

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

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

9
.ncrunch/Avalonia.Markup.UnitTests.netcoreapp1.1.v3.ncrunchproject

@ -0,0 +1,9 @@
<ProjectConfiguration>
<Settings>
<IgnoredTests>
<NamedTestSelector>
<TestName>Avalonia.Markup.UnitTests.Data.Plugins.DataAnnotationsValidationPluginTests.Produces_Aggregate_BindingNotificationsx</TestName>
</NamedTestSelector>
</IgnoredTests>
</Settings>
</ProjectConfiguration>

1
.ncrunch/Avalonia.Skia.RenderTests.v3.ncrunchproject

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

3
.travis.yml

@ -3,13 +3,14 @@ os:
- linux
- osx
dist: trusty
osx_image: xcode8.3
env:
global:
- DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
- DOTNET_CLI_TELEMETRY_OPTOUT=1
mono:
- latest
dotnet: 1.0.1
dotnet: 2.0.0
script:
- ./build.sh --target "Travis" --platform "Mono" --configuration "Release"
notifications:

4
appveyor.yml

@ -17,9 +17,9 @@ init:
- ps: if (Test-Path env:nuget_address) {[System.IO.File]::AppendAllText("C:\Windows\System32\drivers\etc\hosts", "`n$($env:nuget_address)`tapi.nuget.org")}
install:
- if not exist gtk-sharp-2.12.26.msi appveyor DownloadFile http://download.xamarin.com/GTKforWindows/Windows/gtk-sharp-2.12.26.msi
- if not exist dotnet-1.0.1.exe appveyor DownloadFile https://go.microsoft.com/fwlink/?linkid=843448 -FileName "dotnet-1.0.1.exe"
- if not exist dotnet-2.0.0.exe appveyor DownloadFile https://download.microsoft.com/download/0/F/D/0FD852A4-7EA1-4E2A-983A-0484AC19B92C/dotnet-sdk-2.0.0-win-x64.exe -FileName "dotnet-2.0.0.exe"
- ps: Start-Process -FilePath "msiexec" -ArgumentList "/i gtk-sharp-2.12.26.msi /quiet /qn /norestart" -Wait
- ps: Start-Process -FilePath "dotnet-1.0.1.exe" -ArgumentList "/quiet" -Wait
- ps: Start-Process -FilePath "dotnet-2.0.0.exe" -ArgumentList "/quiet" -Wait
- cmd: set PATH=%programfiles(x86)%\GtkSharp\2.12\bin\;%PATH%
before_build:
- git submodule update --init

2
build.cake

@ -162,7 +162,7 @@ void RunCoreTest(string project, Parameters parameters, bool coreOnly = false)
project = System.IO.Path.Combine(project, System.IO.Path.GetFileName(project)+".csproj");
Information("Running tests from " + project);
DotNetCoreRestore(project);
var frameworks = new List<string>(){"netcoreapp1.1"};
var frameworks = new List<string>(){"netcoreapp2.0"};
if(parameters.IsRunningOnWindows)
frameworks.Add("net461");
foreach(var fw in frameworks)

2
build/XUnit.props

@ -9,7 +9,7 @@
<PackageReference Include="xunit.runner.console" Version="2.2.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp1.1'">
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp2.0'">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />
</ItemGroup>
<PropertyGroup>

13
docs/guidelines/build.md

@ -80,10 +80,11 @@ mono ./samples/ControlCatalog.Desktop/bin/Debug/ControlCatalog.Desktop.exe
### Building Avalonia in MonoDevelop
Unless you have a very current version of monodevelop (6.1.x or newer), it is necessary to manually
restore the Nuget depdendencies as [mentioned above](#restore-nuget-packages). You must then
disable MonoDevelop's inbuilt NuGet package manager add-in by going to `Tools -> Add-in Manager` or
it will complain that a newer version of NuGet is needed.
Flatpak version will *NOT* work. Version from https://github.com/cra0zy/monodevelop-run-installer/ might work if you are very lucky. Make sure that you have the latest version of Mono (from alpha update channel) and .NET Core SDK. Make sure to follow `FrameworkPathOverride` workaround from https://github.com/dotnet/sdk/issues/335
### Building and running Avalonia in Rider
For Linux/OSX you'll probably need to apply workaround from https://github.com/dotnet/sdk/issues/335
Just add `export FrameworkPathOverride=/usr/lib/mono/4.6.1-api` (or `export FrameworkPathOverride=/Library/Frameworks/Mono.framework/Versions/Current/lib/mono/4.6.1-api` for OSX)
Finally, select the `Debug | Mono` or `Release | Mono` build configuration and you should be good to
go!

61
packages.cake

@ -43,7 +43,6 @@ public class Packages
}
}
//new NuSpecDependency() { Id = "System.Threading.ThreadPool", TargetFramework = "netcoreapp1.0", Version = "4.3.0" },
public Packages(ICakeContext context, Parameters parameters)
{
// NUGET NUSPECS
@ -186,12 +185,12 @@ public class Packages
};
var coreLibrariesFiles = coreLibraries.Select((lib) => {
return (FilePath)context.File(lib[0] + lib[1] + "/bin/" + parameters.DirSuffix + "/netstandard1.3/" + lib[1] + lib[2]);
return (FilePath)context.File(lib[0] + lib[1] + "/bin/" + parameters.DirSuffix + "/netstandard2.0/" + lib[1] + lib[2]);
}).ToList();
var coreLibrariesNuSpecContent = coreLibrariesFiles.Select((file) => {
return new NuSpecContent {
Source = file.FullPath, Target = "lib/netstandard1.3"
Source = file.FullPath, Target = "lib/netstandard2.0"
};
});
@ -203,14 +202,14 @@ public class Packages
var netcoreappCoreLibrariesNuSpecContent = coreLibrariesFiles.Select((file) => {
return new NuSpecContent {
Source = file.FullPath, Target = "lib/netcoreapp1.0"
Source = file.FullPath, Target = "lib/netcoreapp2.0"
};
});
var net45RuntimePlatformExtensions = new [] {".xml", ".dll"};
var net45RuntimePlatform = net45RuntimePlatformExtensions.Select(libSuffix => {
return new NuSpecContent {
Source = ((FilePath)context.File("./src/Avalonia.DotNetFrameworkRuntime/bin/" + parameters.DirSuffix + "/Avalonia.DotNetFrameworkRuntime" + libSuffix)).FullPath,
Source = ((FilePath)context.File("./src/Avalonia.DotNetFrameworkRuntime/bin/" + parameters.DirSuffix + "/net461/Avalonia.DotNetFrameworkRuntime" + libSuffix)).FullPath,
Target = "lib/net45"
};
});
@ -218,8 +217,8 @@ public class Packages
var netCoreRuntimePlatformExtensions = new [] {".xml", ".dll"};
var netCoreRuntimePlatform = netCoreRuntimePlatformExtensions.Select(libSuffix => {
return new NuSpecContent {
Source = ((FilePath)context.File("./src/Avalonia.DotNetCoreRuntime/bin/" + parameters.DirSuffix + "/netcoreapp1.0/Avalonia.DotNetCoreRuntime" + libSuffix)).FullPath,
Target = "lib/netcoreapp1.0"
Source = ((FilePath)context.File("./src/Avalonia.DotNetCoreRuntime/bin/" + parameters.DirSuffix + "/netcoreapp2.0/Avalonia.DotNetCoreRuntime" + libSuffix)).FullPath,
Target = "lib/netcoreapp2.0"
};
});
@ -238,15 +237,15 @@ public class Packages
new NuSpecDependency() { Id = "Sprache", Version = SpracheVersion },
new NuSpecDependency() { Id = "System.Reactive", Version = SystemReactiveVersion },
//.NET Core
new NuSpecDependency() { Id = "System.Threading.ThreadPool", TargetFramework = "netcoreapp1.0", Version = "4.3.0" },
new NuSpecDependency() { Id = "Microsoft.Extensions.DependencyModel", TargetFramework = "netcoreapp1.0", Version = "1.1.0" },
new NuSpecDependency() { Id = "NETStandard.Library", TargetFramework = "netcoreapp1.0", Version = "1.6.0" },
new NuSpecDependency() { Id = "Splat", TargetFramework = "netcoreapp1.0", Version = SplatVersion },
new NuSpecDependency() { Id = "Serilog", TargetFramework = "netcoreapp1.0", Version = SerilogVersion },
new NuSpecDependency() { Id = "Sprache", TargetFramework = "netcoreapp1.0", Version = SpracheVersion },
new NuSpecDependency() { Id = "System.Reactive", TargetFramework = "netcoreapp1.0", Version = SystemReactiveVersion },
new NuSpecDependency() { Id = "System.Threading.ThreadPool", TargetFramework = "netcoreapp2.0", Version = "4.3.0" },
new NuSpecDependency() { Id = "Microsoft.Extensions.DependencyModel", TargetFramework = "netcoreapp2.0", Version = "1.1.0" },
new NuSpecDependency() { Id = "NETStandard.Library", TargetFramework = "netcoreapp2.0", Version = "1.6.0" },
new NuSpecDependency() { Id = "Splat", TargetFramework = "netcoreapp2.0", Version = SplatVersion },
new NuSpecDependency() { Id = "Serilog", TargetFramework = "netcoreapp2.0", Version = SerilogVersion },
new NuSpecDependency() { Id = "Sprache", TargetFramework = "netcoreapp2.0", Version = SpracheVersion },
new NuSpecDependency() { Id = "System.Reactive", TargetFramework = "netcoreapp2.0", Version = SystemReactiveVersion },
}
.Deps(new string[]{null, "netcoreapp1.0"},
.Deps(new string[]{null, "netcoreapp2.0"},
"System.ValueTuple", "System.ComponentModel.TypeConverter", "System.ComponentModel.Primitives",
"System.Runtime.Serialization.Primitives", "System.Xml.XmlDocument", "System.Xml.ReaderWriter")
.ToArray(),
@ -269,9 +268,9 @@ public class Packages
},
Files = new []
{
new NuSpecContent { Source = "Avalonia.HtmlRenderer.dll", Target = "lib/netstandard1.3" }
new NuSpecContent { Source = "Avalonia.HtmlRenderer.dll", Target = "lib/netstandard2.0" }
},
BasePath = context.Directory("./src/Avalonia.HtmlRenderer/bin/" + parameters.DirSuffix + "/netstandard1.3"),
BasePath = context.Directory("./src/Avalonia.HtmlRenderer/bin/" + parameters.DirSuffix + "/netstandard2.0"),
OutputDirectory = parameters.NugetRoot
}
};
@ -331,7 +330,7 @@ public class Packages
Files = new []
{
new NuSpecContent { Source = "Avalonia.Win32/bin/" + parameters.DirSuffix + "/Avalonia.Win32.dll", Target = "lib/net45" },
new NuSpecContent { Source = "Avalonia.Win32.NetStandard/bin/" + parameters.DirSuffix + "/netstandard1.3/Avalonia.Win32.dll", Target = "lib/netstandard1.3" }
new NuSpecContent { Source = "Avalonia.Win32.NetStandard/bin/" + parameters.DirSuffix + "/netstandard2.0/Avalonia.Win32.dll", Target = "lib/netstandard2.0" }
},
BasePath = context.Directory("./src/Windows"),
OutputDirectory = parameters.NugetRoot
@ -352,9 +351,9 @@ public class Packages
},
Files = new []
{
new NuSpecContent { Source = "Avalonia.Direct2D1.dll", Target = "lib/netstandard1.3" }
new NuSpecContent { Source = "Avalonia.Direct2D1.dll", Target = "lib/netstandard2.0" }
},
BasePath = context.Directory("./src/Windows/Avalonia.Direct2D1/bin/" + parameters.DirSuffix + "/netstandard1.3"),
BasePath = context.Directory("./src/Windows/Avalonia.Direct2D1/bin/" + parameters.DirSuffix + "/netstandard2.0"),
OutputDirectory = parameters.NugetRoot
},
///////////////////////////////////////////////////////////////////////////////
@ -386,9 +385,9 @@ public class Packages
},
Files = new []
{
new NuSpecContent { Source = "Avalonia.Gtk3.dll", Target = "lib/netstandard1.3" }
new NuSpecContent { Source = "Avalonia.Gtk3.dll", Target = "lib/netstandard2.0" }
},
BasePath = context.Directory("./src/Gtk/Avalonia.Gtk3/bin/" + parameters.DirSuffix + "/netstandard1.3"),
BasePath = context.Directory("./src/Gtk/Avalonia.Gtk3/bin/" + parameters.DirSuffix + "/netstandard2.0"),
OutputDirectory = parameters.NugetRoot
},
///////////////////////////////////////////////////////////////////////////////
@ -418,18 +417,18 @@ public class Packages
{
new NuSpecDependency() { Id = "Avalonia", Version = parameters.Version },
new NuSpecDependency() { Id = "SkiaSharp", Version = SkiaSharpVersion },
new NuSpecDependency() { Id = "Avalonia", Version = parameters.Version, TargetFramework="netcoreapp1.1" },
new NuSpecDependency() { Id = "SkiaSharp", Version = SkiaSharpVersion, TargetFramework="netcoreapp1.1" },
new NuSpecDependency() { Id = "Avalonia.Skia.Linux.Natives", Version = SkiaSharpLinuxVersion, TargetFramework="netcoreapp1.1" },
new NuSpecDependency() { Id = "Avalonia", Version = parameters.Version, TargetFramework="netcoreapp2.0" },
new NuSpecDependency() { Id = "SkiaSharp", Version = SkiaSharpVersion, TargetFramework="netcoreapp2.0" },
new NuSpecDependency() { Id = "Avalonia.Skia.Linux.Natives", Version = SkiaSharpLinuxVersion, TargetFramework="netcoreapp2.0" },
new NuSpecDependency() { Id = "Avalonia", Version = parameters.Version, TargetFramework="net461" },
new NuSpecDependency() { Id = "SkiaSharp", Version = SkiaSharpVersion, TargetFramework="net461" },
new NuSpecDependency() { Id = "Avalonia.Skia.Linux.Natives", Version = SkiaSharpLinuxVersion, TargetFramework="net461" }
},
Files = new []
{
new NuSpecContent { Source = "Avalonia.Skia.dll", Target = "lib/netstandard1.3" }
new NuSpecContent { Source = "Avalonia.Skia.dll", Target = "lib/netstandard2.0" }
},
BasePath = context.Directory("./src/Skia/Avalonia.Skia/bin/" + parameters.DirSuffix + "/netstandard1.3"),
BasePath = context.Directory("./src/Skia/Avalonia.Skia/bin/" + parameters.DirSuffix + "/netstandard2.0"),
OutputDirectory = parameters.NugetRoot
},
///////////////////////////////////////////////////////////////////////////////
@ -448,9 +447,9 @@ public class Packages
new NuSpecDependency() { Id = "Avalonia.Skia", TargetFramework="net45", Version = parameters.Version },
new NuSpecDependency() { Id = "Avalonia.Gtk3", TargetFramework="net45", Version = parameters.Version },
//.NET Core
new NuSpecDependency() { Id = "Avalonia.Win32", TargetFramework="netcoreapp1.0", Version = parameters.Version },
new NuSpecDependency() { Id = "Avalonia.Skia", TargetFramework="netcoreapp1.0", Version = parameters.Version },
new NuSpecDependency() { Id = "Avalonia.Gtk3", TargetFramework="netcoreapp1.0", Version = parameters.Version }
new NuSpecDependency() { Id = "Avalonia.Win32", TargetFramework="netcoreapp2.0", Version = parameters.Version },
new NuSpecDependency() { Id = "Avalonia.Skia", TargetFramework="netcoreapp2.0", Version = parameters.Version },
new NuSpecDependency() { Id = "Avalonia.Gtk3", TargetFramework="netcoreapp2.0", Version = parameters.Version }
},
Files = new NuSpecContent[]
{
@ -488,7 +487,7 @@ public class Packages
},
Files = new []
{
new NuSpecContent { Source = "Avalonia.LinuxFramebuffer/bin/" + parameters.DirSuffix + "/netstandard1.3/Avalonia.LinuxFramebuffer.dll", Target = "lib/netstandard1.3" }
new NuSpecContent { Source = "Avalonia.LinuxFramebuffer/bin/" + parameters.DirSuffix + "/netstandard2.0/Avalonia.LinuxFramebuffer.dll", Target = "lib/netstandard2.0" }
},
BasePath = context.Directory("./src/Linux/"),
OutputDirectory = parameters.NugetRoot

2
samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj

@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp1.1</TargetFramework>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>

2
samples/ControlCatalog/ControlCatalog.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.3</TargetFramework>
<TargetFramework>netstandard2.0</TargetFramework>
<EnableDefaultCompileItems>False</EnableDefaultCompileItems>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>

3
samples/ControlCatalog/MainWindow.xaml.cs

@ -1,6 +1,7 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using System;
namespace ControlCatalog
{
@ -19,7 +20,7 @@ namespace ControlCatalog
// so we must refer to this resource DLL statically. For
// now I am doing that here. But we need a better solution!!
var theme = new Avalonia.Themes.Default.DefaultTheme();
theme.FindResource("Button");
theme.TryGetResource("Button", out _);
AvaloniaXamlLoader.Load(this);
}
}

53
samples/ControlCatalog/Pages/ToolTipPage.xaml

@ -1,22 +1,41 @@
<UserControl xmlns="https://github.com/avaloniaui">
<StackPanel Orientation="Vertical" Gap="4">
<TextBlock Classes="h1">ToolTip</TextBlock>
<TextBlock Classes="h2">A control which pops up a hint when a control is hovered</TextBlock>
<StackPanel Orientation="Vertical"
Gap="4">
<TextBlock Classes="h1">ToolTip</TextBlock>
<TextBlock Classes="h2">A control which pops up a hint when a control is hovered</TextBlock>
<StackPanel Orientation="Horizontal"
<Grid RowDefinitions="Auto,Auto"
ColumnDefinitions="Auto,Auto"
Margin="0,16,0,0"
HorizontalAlignment="Center"
Gap="16">
<Border Background="{StyleResource ThemeAccentBrush}"
Padding="48,48,48,48">
<ToolTip.Tip>
<StackPanel>
<TextBlock Classes="h1">ToolTip</TextBlock>
<TextBlock Classes="h2">A control which pops up a hint when a control is hovered</TextBlock>
</StackPanel>
</ToolTip.Tip>
<TextBlock>Hover Here</TextBlock>
</Border>
HorizontalAlignment="Center">
<Border Grid.Column="0"
Grid.Row="1"
Background="{StyleResource ThemeAccentBrush}"
Margin="5"
Padding="50"
ToolTip.Tip="This is a ToolTip">
<TextBlock>Hover Here</TextBlock>
</Border>
<CheckBox Grid.Column="1"
Margin="5"
Grid.Row="0"
IsChecked="{Binding ElementName=Border, Path=(ToolTip.IsOpen)}"
Content="ToolTip Open" />
<Border Name="Border"
Grid.Column="1"
Grid.Row="1"
Background="{StyleResource ThemeAccentBrush}"
Margin="5"
Padding="50"
ToolTip.Placement="Bottom">
<ToolTip.Tip>
<StackPanel>
<TextBlock Classes="h1">ToolTip</TextBlock>
<TextBlock Classes="h2">A control which pops up a hint when a control is hovered</TextBlock>
</StackPanel>
</ToolTip.Tip>
<TextBlock>ToolTip bottom placement</TextBlock>
</Border>
</Grid>
</StackPanel>
</StackPanel>
</UserControl>

2
src/Avalonia.Animation/Avalonia.Animation.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.3</TargetFramework>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">

2
src/Avalonia.Base/Avalonia.Base.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.3</TargetFramework>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<RootNamespace>Avalonia</RootNamespace>
</PropertyGroup>

3
src/Avalonia.Base/AvaloniaPropertyRegistry.cs

@ -47,6 +47,9 @@ namespace Avalonia
{
Dictionary<int, AvaloniaProperty> inner;
// Ensure the type's static ctor has been run.
RuntimeHelpers.RunClassConstructor(ownerType.TypeHandle);
if (_attached.TryGetValue(ownerType, out inner))
{
return inner.Values;

54
src/Avalonia.Base/Collections/AvaloniaDictionary.cs

@ -16,6 +16,7 @@ namespace Avalonia.Collections
/// <typeparam name="TKey">The type of the dictionary key.</typeparam>
/// <typeparam name="TValue">The type of the dictionary value.</typeparam>
public class AvaloniaDictionary<TKey, TValue> : IDictionary<TKey, TValue>,
IDictionary,
INotifyCollectionChanged,
INotifyPropertyChanged
{
@ -51,6 +52,16 @@ namespace Avalonia.Collections
/// <inheritdoc/>
public ICollection<TValue> Values => _inner.Values;
bool IDictionary.IsFixedSize => ((IDictionary)_inner).IsFixedSize;
ICollection IDictionary.Keys => ((IDictionary)_inner).Keys;
ICollection IDictionary.Values => ((IDictionary)_inner).Values;
bool ICollection.IsSynchronized => ((IDictionary)_inner).IsSynchronized;
object ICollection.SyncRoot => ((IDictionary)_inner).SyncRoot;
/// <summary>
/// Gets or sets the named resource.
/// </summary>
@ -89,6 +100,8 @@ namespace Avalonia.Collections
}
}
object IDictionary.this[object key] { get => ((IDictionary)_inner)[key]; set => ((IDictionary)_inner)[key] = value; }
/// <inheritdoc/>
public void Add(TKey key, TValue value)
{
@ -118,10 +131,7 @@ namespace Avalonia.Collections
}
/// <inheritdoc/>
public bool ContainsKey(TKey key)
{
return _inner.ContainsKey(key);
}
public bool ContainsKey(TKey key) => _inner.ContainsKey(key);
/// <inheritdoc/>
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
@ -130,21 +140,16 @@ namespace Avalonia.Collections
}
/// <inheritdoc/>
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return _inner.GetEnumerator();
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => _inner.GetEnumerator();
/// <inheritdoc/>
public bool Remove(TKey key)
{
TValue value;
if (_inner.TryGetValue(key, out value))
if (_inner.TryGetValue(key, out TValue value))
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Count"));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"Item[{key}]"));
if (CollectionChanged != null)
{
var e = new NotifyCollectionChangedEventArgs(
@ -163,16 +168,13 @@ namespace Avalonia.Collections
}
/// <inheritdoc/>
public bool TryGetValue(TKey key, out TValue value)
{
return _inner.TryGetValue(key, out value);
}
public bool TryGetValue(TKey key, out TValue value) => _inner.TryGetValue(key, out value);
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator()
{
return _inner.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() => _inner.GetEnumerator();
/// <inheritdoc/>
void ICollection.CopyTo(Array array, int index) => ((ICollection)_inner).CopyTo(array, index);
/// <inheritdoc/>
void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
@ -192,6 +194,18 @@ namespace Avalonia.Collections
return Remove(item.Key);
}
/// <inheritdoc/>
void IDictionary.Add(object key, object value) => Add((TKey)key, (TValue)value);
/// <inheritdoc/>
bool IDictionary.Contains(object key) => ((IDictionary) _inner).Contains(key);
/// <inheritdoc/>
IDictionaryEnumerator IDictionary.GetEnumerator() => ((IDictionary)_inner).GetEnumerator();
/// <inheritdoc/>
void IDictionary.Remove(object key) => Remove((TKey)key);
private void NotifyAdd(TKey key, TValue value)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Count"));

51
src/Avalonia.Controls/Application.cs

@ -29,7 +29,7 @@ namespace Avalonia
/// method.
/// - Tracks the lifetime of the application.
/// </remarks>
public class Application : IGlobalDataTemplates, IGlobalStyles, IStyleRoot, IApplicationLifecycle
public class Application : IApplicationLifecycle, IGlobalDataTemplates, IGlobalStyles, IStyleRoot, IResourceNode
{
/// <summary>
/// The application-global data templates.
@ -40,6 +40,7 @@ namespace Avalonia
new Lazy<IClipboard>(() => (IClipboard)AvaloniaLocator.Current.GetService(typeof(IClipboard)));
private readonly Styler _styler = new Styler();
private Styles _styles;
private IResourceDictionary _resources;
/// <summary>
/// Initializes a new instance of the <see cref="Application"/> class.
@ -49,6 +50,9 @@ namespace Avalonia
OnExit += OnExiting;
}
/// <inheritdoc/>
public event EventHandler<ResourcesChangedEventArgs> ResourcesChanged;
/// <summary>
/// Gets the current instance of the <see cref="Application"/> class.
/// </summary>
@ -97,6 +101,34 @@ namespace Avalonia
/// </summary>
public IClipboard Clipboard => _clipboard.Value;
/// <summary>
/// Gets the application's global resource dictionary.
/// </summary>
public IResourceDictionary Resources
{
get => _resources ?? (Resources = new ResourceDictionary());
set
{
Contract.Requires<ArgumentNullException>(value != null);
var hadResources = false;
if (_resources != null)
{
hadResources = _resources.Count > 0;
_resources.ResourcesChanged -= ResourcesChanged;
}
_resources = value;
_resources.ResourcesChanged += ResourcesChanged;
if (hadResources || _resources.Count > 0)
{
ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs());
}
}
}
/// <summary>
/// Gets the application's global styles.
/// </summary>
@ -119,6 +151,12 @@ namespace Avalonia
/// <inheritdoc/>
bool IStyleHost.IsStylesInitialized => _styles != null;
/// <inheritdoc/>
bool IResourceProvider.HasResources => _resources?.Count > 0;
/// <inheritdoc/>
IResourceNode IResourceNode.ResourceParent => null;
/// <summary>
/// Initializes the application by loading XAML etc.
/// </summary>
@ -145,13 +183,20 @@ namespace Avalonia
{
OnExit?.Invoke(this, EventArgs.Empty);
}
/// <inheritdoc/>
bool IResourceProvider.TryGetResource(string key, out object value)
{
value = null;
return (_resources?.TryGetResource(key, out value) ?? false) ||
Styles.TryGetResource(key, out value);
}
/// <summary>
/// Sent when the application is exiting.
/// </summary>
public event EventHandler OnExit;
/// <summary>
/// Called when the application is exiting.
/// </summary>

2
src/Avalonia.Controls/Avalonia.Controls.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.3</TargetFramework>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">

134
src/Avalonia.Controls/Control.cs

@ -97,8 +97,9 @@ namespace Avalonia.Controls
private bool _isAttachedToLogicalTree;
private IAvaloniaList<ILogical> _logicalChildren;
private INameScope _nameScope;
private bool _styled;
private IResourceDictionary _resources;
private Styles _styles;
private bool _styled;
private Subject<IStyleable> _styleDetach = new Subject<IStyleable>();
/// <summary>
@ -153,6 +154,11 @@ namespace Avalonia.Controls
/// </remarks>
public event EventHandler Initialized;
/// <summary>
/// Occurs when a resource in this control or a parent control has changed.
/// </summary>
public event EventHandler<ResourcesChangedEventArgs> ResourcesChanged;
/// <summary>
/// Gets or sets the name of the control.
/// </summary>
@ -262,7 +268,32 @@ namespace Avalonia.Controls
/// each control may in addition define its own styles which are applied to the control
/// itself and its children.
/// </remarks>
public Styles Styles => _styles ?? (_styles = new Styles());
public Styles Styles
{
get { return _styles ?? (Styles = new Styles()); }
set
{
Contract.Requires<ArgumentNullException>(value != null);
if (_styles != value)
{
if (_styles != null)
{
(_styles as ISetStyleParent)?.SetParent(null);
_styles.ResourcesChanged -= ThisResourcesChanged;
}
_styles = value;
if (value is ISetStyleParent setParent && setParent.ResourceParent == null)
{
setParent.SetParent(this);
}
_styles.ResourcesChanged += ThisResourcesChanged;
}
}
}
/// <summary>
/// Gets the control's logical parent.
@ -278,6 +309,34 @@ namespace Avalonia.Controls
set { SetValue(ContextMenuProperty, value); }
}
/// <summary>
/// Gets or sets the control's resource dictionary.
/// </summary>
public IResourceDictionary Resources
{
get => _resources ?? (Resources = new ResourceDictionary());
set
{
Contract.Requires<ArgumentNullException>(value != null);
var hadResources = false;
if (_resources != null)
{
hadResources = _resources.Count > 0;
_resources.ResourcesChanged -= ThisResourcesChanged;
}
_resources = value;
_resources.ResourcesChanged += ThisResourcesChanged;
if (hadResources || _resources.Count > 0)
{
((ILogical)this).NotifyResourcesChanged(new ResourcesChangedEventArgs());
}
}
}
/// <summary>
/// Gets or sets a user-defined object attached to the control.
/// </summary>
@ -296,9 +355,35 @@ namespace Avalonia.Controls
internal set { SetValue(TemplatedParentProperty, value); }
}
/// <summary>
/// Gets the control's logical children.
/// </summary>
protected IAvaloniaList<ILogical> LogicalChildren
{
get
{
if (_logicalChildren == null)
{
var list = new AvaloniaList<ILogical>();
list.ResetBehavior = ResetBehavior.Remove;
list.Validate = ValidateLogicalChild;
list.CollectionChanged += LogicalChildrenCollectionChanged;
_logicalChildren = list;
}
return _logicalChildren;
}
}
/// <inheritdoc/>
bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null;
/// <summary>
/// Gets the <see cref="Classes"/> collection in a form that allows adding and removing
/// pseudoclasses.
/// </summary>
protected IPseudoClasses PseudoClasses => Classes;
/// <summary>
/// Gets a value indicating whether the element is attached to a rooted logical tree.
/// </summary>
@ -314,6 +399,12 @@ namespace Avalonia.Controls
/// </summary>
IAvaloniaReadOnlyList<ILogical> ILogical.LogicalChildren => LogicalChildren;
/// <inheritdoc/>
bool IResourceProvider.HasResources => _resources?.Count > 0 || Styles.HasResources;
/// <inheritdoc/>
IResourceNode IResourceNode.ResourceParent => ((IStyleHost)this).StylingParent as IResourceNode;
/// <inheritdoc/>
IAvaloniaReadOnlyList<string> IStyleable.Classes => Classes;
@ -390,31 +481,24 @@ namespace Avalonia.Controls
this.OnDetachedFromLogicalTreeCore(e);
}
/// <summary>
/// Gets the control's logical children.
/// </summary>
protected IAvaloniaList<ILogical> LogicalChildren
/// <inheritdoc/>
void ILogical.NotifyResourcesChanged(ResourcesChangedEventArgs e)
{
get
{
if (_logicalChildren == null)
{
var list = new AvaloniaList<ILogical>();
list.ResetBehavior = ResetBehavior.Remove;
list.Validate = ValidateLogicalChild;
list.CollectionChanged += LogicalChildrenCollectionChanged;
_logicalChildren = list;
}
ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs());
return _logicalChildren;
foreach (var child in LogicalChildren)
{
child.NotifyResourcesChanged(e);
}
}
/// <summary>
/// Gets the <see cref="Classes"/> collection in a form that allows adding and removing
/// pseudoclasses.
/// </summary>
protected IPseudoClasses PseudoClasses => Classes;
/// <inheritdoc/>
bool IResourceProvider.TryGetResource(string key, out object value)
{
value = null;
return (_resources?.TryGetResource(key, out value) ?? false) ||
(_styles?.TryGetResource(key, out value) ?? false);
}
/// <summary>
/// Sets the control's logical parent.
@ -450,6 +534,7 @@ namespace Avalonia.Controls
}
_parent = (IControl)parent;
((ILogical)this).NotifyResourcesChanged(new ResourcesChangedEventArgs());
if (_parent is IStyleRoot || _parent?.IsAttachedToLogicalTree == true || this is IStyleRoot)
{
@ -841,5 +926,10 @@ namespace Avalonia.Controls
}
}
}
private void ThisResourcesChanged(object sender, ResourcesChangedEventArgs e)
{
((ILogical)this).NotifyResourcesChanged(e);
}
}
}

2
src/Avalonia.Controls/DropDown.cs

@ -120,7 +120,7 @@ namespace Avalonia.Controls
/// <inheritdoc/>
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
if (!IsDropDownOpen && ((IVisual)e.Source).GetVisualRoot() != typeof(PopupRoot))
if (!IsDropDownOpen && ((IVisual)e.Source).GetVisualRoot() is PopupRoot)
{
IsDropDownOpen = true;
e.Handled = true;

1
src/Avalonia.Controls/IControl.cs

@ -20,6 +20,7 @@ namespace Avalonia.Controls
ILayoutable,
IInputElement,
INamed,
IResourceNode,
IStyleable,
IStyleHost
{

7
src/Avalonia.Controls/Platform/IWindowImpl.cs

@ -31,7 +31,7 @@ namespace Avalonia.Platform
IDisposable ShowDialog();
/// <summary>
/// Enables of disables system window decorations (title bar, buttons, etc)
/// Enables or disables system window decorations (title bar, buttons, etc)
/// </summary>
void SetSystemDecorations(bool enabled);
@ -39,5 +39,10 @@ namespace Avalonia.Platform
/// Sets the icon of this window.
/// </summary>
void SetIcon(IWindowIconImpl icon);
/// <summary>
/// Enables or disables the taskbar icon
/// </summary>
void ShowTaskbarIcon(bool value);
}
}

2
src/Avalonia.Controls/Presenters/TextPresenter.cs

@ -115,7 +115,7 @@ namespace Avalonia.Controls.Presenters
if (_highlightBrush == null)
{
_highlightBrush = (IBrush)this.FindStyleResource("HighlightBrush");
_highlightBrush = (IBrush)this.FindResource("HighlightBrush");
}
foreach (var rect in rects)

26
src/Avalonia.Controls/Primitives/Popup.cs

@ -277,7 +277,7 @@ namespace Avalonia.Controls.Primitives
{
base.OnDetachedFromLogicalTree(e);
_topLevel = null;
if (_popupRoot != null)
{
((ISetLogicalParent)_popupRoot).SetParent(null);
@ -327,34 +327,40 @@ namespace Avalonia.Controls.Primitives
/// </summary>
/// <returns>The popup's position in screen coordinates.</returns>
protected virtual Point GetPosition()
{
return GetPosition(PlacementTarget ?? this.GetVisualParent<Control>(), PlacementMode, PopupRoot,
HorizontalOffset, VerticalOffset);
}
internal static Point GetPosition(Control target, PlacementMode placement, PopupRoot popupRoot, double horizontalOffset, double verticalOffset)
{
var zero = default(Point);
var mode = PlacementMode;
var target = PlacementTarget ?? this.GetVisualParent<Control>();
var mode = placement;
if (target?.GetVisualRoot() == null)
{
mode = PlacementMode.Pointer;
}
}
switch (mode)
{
case PlacementMode.Pointer:
if(PopupRoot != null)
if (popupRoot != null)
{
// Scales the Horizontal and Vertical offset to screen co-ordinates.
var screenOffset = new Point(HorizontalOffset * (PopupRoot as ILayoutRoot).LayoutScaling, VerticalOffset * (PopupRoot as ILayoutRoot).LayoutScaling);
return (((IInputRoot)PopupRoot)?.MouseDevice?.Position ?? default(Point)) + screenOffset;
var screenOffset = new Point(horizontalOffset * (popupRoot as ILayoutRoot).LayoutScaling,
verticalOffset * (popupRoot as ILayoutRoot).LayoutScaling);
return (((IInputRoot)popupRoot)?.MouseDevice?.Position ?? default(Point)) + screenOffset;
}
return default(Point);
case PlacementMode.Bottom:
return target?.PointToScreen(new Point(0 + HorizontalOffset, target.Bounds.Height + VerticalOffset)) ?? zero;
return target?.PointToScreen(new Point(0 + horizontalOffset, target.Bounds.Height + verticalOffset)) ??
zero;
case PlacementMode.Right:
return target?.PointToScreen(new Point(target.Bounds.Width + HorizontalOffset, 0 + VerticalOffset)) ?? zero;
return target?.PointToScreen(new Point(target.Bounds.Width + horizontalOffset, 0 + verticalOffset)) ?? zero;
default:
throw new InvalidOperationException("Invalid value for Popup.PlacementMode");

236
src/Avalonia.Controls/ToolTip.cs

@ -3,11 +3,7 @@
using System;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Threading;
using Avalonia.VisualTree;
namespace Avalonia.Controls
{
@ -29,29 +25,50 @@ namespace Avalonia.Controls
AvaloniaProperty.RegisterAttached<ToolTip, Control, object>("Tip");
/// <summary>
/// The popup window used to display the active tooltip.
/// Defines the ToolTip.IsOpen attached property.
/// </summary>
private static PopupRoot s_popup;
public static readonly AttachedProperty<bool> IsOpenProperty =
AvaloniaProperty.RegisterAttached<ToolTip, Control, bool>("IsOpen");
/// <summary>
/// The control that the currently visible tooltip is attached to.
/// Defines the ToolTip.Placement property.
/// </summary>
private static Control s_current;
public static readonly AttachedProperty<PlacementMode> PlacementProperty =
AvaloniaProperty.RegisterAttached<Popup, Control, PlacementMode>("Placement", defaultValue: PlacementMode.Pointer);
/// <summary>
/// Observable fired when a tooltip should be displayed for a control. The output from this
/// observable is throttled and calls <see cref="ShowToolTip(Control)"/> when the time
/// period expires.
/// Defines the ToolTip.HorizontalOffset property.
/// </summary>
private static readonly Subject<Control> s_show = new Subject<Control>();
public static readonly AttachedProperty<double> HorizontalOffsetProperty =
AvaloniaProperty.RegisterAttached<Popup, Control, double>("HorizontalOffset");
/// <summary>
/// Defines the ToolTip.VerticalOffset property.
/// </summary>
public static readonly AttachedProperty<double> VerticalOffsetProperty =
AvaloniaProperty.RegisterAttached<Popup, Control, double>("VerticalOffset", 20);
/// <summary>
/// Defines the ToolTip.ShowDelay property.
/// </summary>
public static readonly AttachedProperty<int> ShowDelayProperty =
AvaloniaProperty.RegisterAttached<Popup, Control, int>("ShowDelay", 400);
/// <summary>
/// Stores the curernt <see cref="ToolTip"/> instance in the control.
/// </summary>
private static readonly AttachedProperty<ToolTip> ToolTipProperty =
AvaloniaProperty.RegisterAttached<ToolTip, Control, ToolTip>("ToolTip");
private PopupRoot _popup;
/// <summary>
/// Initializes static members of the <see cref="ToolTip"/> class.
/// </summary>
static ToolTip()
{
TipProperty.Changed.Subscribe(TipChanged);
s_show.Throttle(TimeSpan.FromSeconds(0.5), AvaloniaScheduler.Instance).Subscribe(ShowToolTip);
TipProperty.Changed.Subscribe(ToolTipService.Instance.TipChanged);
IsOpenProperty.Changed.Subscribe(IsOpenChanged);
}
/// <summary>
@ -77,101 +94,160 @@ namespace Avalonia.Controls
}
/// <summary>
/// called when the <see cref="TipProperty"/> property changes on a control.
/// Gets the value of the ToolTip.IsOpen attached property.
/// </summary>
/// <param name="e">The event args.</param>
private static void TipChanged(AvaloniaPropertyChangedEventArgs e)
/// <param name="element">The control to get the property from.</param>
/// <returns>
/// A value indicating whether the tool tip is visible.
/// </returns>
public static bool GetIsOpen(Control element)
{
var control = (Control)e.Sender;
return element.GetValue(IsOpenProperty);
}
if (e.OldValue != null)
{
control.PointerEnter -= ControlPointerEnter;
control.PointerLeave -= ControlPointerLeave;
}
/// <summary>
/// Sets the value of the ToolTip.IsOpen attached property.
/// </summary>
/// <param name="element">The control to get the property from.</param>
/// <param name="value">A value indicating whether the tool tip is visible.</param>
public static void SetIsOpen(Control element, bool value)
{
element.SetValue(IsOpenProperty, value);
}
if (e.NewValue != null)
{
control.PointerEnter += ControlPointerEnter;
control.PointerLeave += ControlPointerLeave;
}
/// <summary>
/// Gets the value of the ToolTip.Placement attached property.
/// </summary>
/// <param name="element">The control to get the property from.</param>
/// <returns>
/// A value indicating how the tool tip is positioned.
/// </returns>
public static PlacementMode GetPlacement(Control element)
{
return element.GetValue(PlacementProperty);
}
/// <summary>
/// Shows a tooltip for the specified control.
/// Sets the value of the ToolTip.Placement attached property.
/// </summary>
/// <param name="control">The control.</param>
private static void ShowToolTip(Control control)
/// <param name="element">The control to get the property from.</param>
/// <param name="value">A value indicating how the tool tip is positioned.</param>
public static void SetPlacement(Control element, PlacementMode value)
{
if (control != null && control.IsVisible && control.GetVisualRoot() != null)
{
var cp = (control.GetVisualRoot() as IInputRoot)?.MouseDevice?.GetPosition(control);
element.SetValue(PlacementProperty, value);
}
if (cp.HasValue && control.IsVisible && new Rect(control.Bounds.Size).Contains(cp.Value))
{
var position = control.PointToScreen(cp.Value) + new Vector(0, 22);
if (s_popup == null)
{
s_popup = new PopupRoot();
s_popup.Content = new ToolTip();
}
else
{
((ISetLogicalParent)s_popup).SetParent(null);
}
((ISetLogicalParent)s_popup).SetParent(control);
((ToolTip)s_popup.Content).Content = GetTip(control);
s_popup.Position = position;
s_popup.Show();
s_current = control;
}
}
/// <summary>
/// Gets the value of the ToolTip.HorizontalOffset attached property.
/// </summary>
/// <param name="element">The control to get the property from.</param>
/// <returns>
/// A value indicating how the tool tip is positioned.
/// </returns>
public static double GetHorizontalOffset(Control element)
{
return element.GetValue(HorizontalOffsetProperty);
}
/// <summary>
/// Sets the value of the ToolTip.HorizontalOffset attached property.
/// </summary>
/// <param name="element">The control to get the property from.</param>
/// <param name="value">A value indicating how the tool tip is positioned.</param>
public static void SetHorizontalOffset(Control element, double value)
{
element.SetValue(HorizontalOffsetProperty, value);
}
/// <summary>
/// Gets the value of the ToolTip.VerticalOffset attached property.
/// </summary>
/// <param name="element">The control to get the property from.</param>
/// <returns>
/// A value indicating how the tool tip is positioned.
/// </returns>
public static double GetVerticalOffset(Control element)
{
return element.GetValue(VerticalOffsetProperty);
}
/// <summary>
/// Sets the value of the ToolTip.VerticalOffset attached property.
/// </summary>
/// <param name="element">The control to get the property from.</param>
/// <param name="value">A value indicating how the tool tip is positioned.</param>
public static void SetVerticalOffset(Control element, double value)
{
element.SetValue(VerticalOffsetProperty, value);
}
/// <summary>
/// Called when the pointer enters a control with an attached tooltip.
/// Gets the value of the ToolTip.ShowDelay attached property.
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event args.</param>
private static void ControlPointerEnter(object sender, PointerEventArgs e)
/// <param name="element">The control to get the property from.</param>
/// <returns>
/// A value indicating the time, in milliseconds, before a tool tip opens.
/// </returns>
public static int GetShowDelay(Control element)
{
s_current = (Control)sender;
s_show.OnNext(s_current);
return element.GetValue(ShowDelayProperty);
}
/// <summary>
/// Called when the pointer leaves a control with an attached tooltip.
/// Sets the value of the ToolTip.ShowDelay attached property.
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event args.</param>
private static void ControlPointerLeave(object sender, PointerEventArgs e)
/// <param name="element">The control to get the property from.</param>
/// <param name="value">A value indicating the time, in milliseconds, before a tool tip opens.</param>
public static void SetShowDelay(Control element, int value)
{
var control = (Control)sender;
element.SetValue(ShowDelayProperty, value);
}
private static void IsOpenChanged(AvaloniaPropertyChangedEventArgs e)
{
var control = (Control)e.Sender;
if (control == s_current)
if ((bool)e.NewValue)
{
if (s_popup != null)
var tip = GetTip(control);
if (tip == null) return;
var toolTip = control.GetValue(ToolTipProperty);
if (toolTip == null || (tip != toolTip && tip != toolTip.Content))
{
DisposeTooltip();
s_show.OnNext(null);
toolTip?.Close();
toolTip = tip as ToolTip ?? new ToolTip { Content = tip };
control.SetValue(ToolTipProperty, toolTip);
}
toolTip.Open(control);
}
else
{
var toolTip = control.GetValue(ToolTipProperty);
toolTip?.Close();
}
}
private static void DisposeTooltip()
private void Open(Control control)
{
if (s_popup != null)
{
// Clear the ToolTip's Content in case it has control content: this will
// reset its visual parent allowing it to be used again.
((ToolTip)s_popup.Content).Content = null;
Close();
_popup = new PopupRoot { Content = this };
((ISetLogicalParent)_popup).SetParent(control);
_popup.Position = Popup.GetPosition(control, GetPlacement(control), _popup,
GetHorizontalOffset(control), GetVerticalOffset(control));
_popup.Show();
}
// Dispose of the popup.
s_popup.Dispose();
s_popup = null;
private void Close()
{
if (_popup != null)
{
_popup.Content = null;
_popup.Hide();
_popup = null;
}
}
}

98
src/Avalonia.Controls/ToolTipService.cs

@ -0,0 +1,98 @@
using System;
using Avalonia.Input;
using Avalonia.Threading;
namespace Avalonia.Controls
{
/// <summary>
/// Handeles <see cref="ToolTip"/> interaction with controls.
/// </summary>
internal sealed class ToolTipService
{
public static ToolTipService Instance { get; } = new ToolTipService();
private DispatcherTimer _timer;
private ToolTipService() { }
/// <summary>
/// called when the <see cref="ToolTip.TipProperty"/> property changes on a control.
/// </summary>
/// <param name="e">The event args.</param>
internal void TipChanged(AvaloniaPropertyChangedEventArgs e)
{
var control = (Control)e.Sender;
if (e.OldValue != null)
{
control.PointerEnter -= ControlPointerEnter;
control.PointerLeave -= ControlPointerLeave;
}
if (e.NewValue != null)
{
control.PointerEnter += ControlPointerEnter;
control.PointerLeave += ControlPointerLeave;
}
}
/// <summary>
/// Called when the pointer enters a control with an attached tooltip.
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event args.</param>
private void ControlPointerEnter(object sender, PointerEventArgs e)
{
StopTimer();
var control = (Control)sender;
var showDelay = ToolTip.GetShowDelay(control);
if (showDelay == 0)
{
Open(control);
}
else
{
StartShowTimer(showDelay, control);
}
}
/// <summary>
/// Called when the pointer leaves a control with an attached tooltip.
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event args.</param>
private void ControlPointerLeave(object sender, PointerEventArgs e)
{
var control = (Control)sender;
Close(control);
}
private void StartShowTimer(int showDelay, Control control)
{
_timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(showDelay) };
_timer.Tick += (o, e) => Open(control);
_timer.Start();
}
private void Open(Control control)
{
StopTimer();
ToolTip.SetIsOpen(control, true);
}
private void Close(Control control)
{
StopTimer();
ToolTip.SetIsOpen(control, false);
}
private void StopTimer()
{
_timer?.Stop();
_timer = null;
}
}
}

24
src/Avalonia.Controls/TopLevel.cs

@ -10,9 +10,11 @@ using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Layout;
using Avalonia.Logging;
using Avalonia.LogicalTree;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Styling;
using Avalonia.Utilities;
using Avalonia.VisualTree;
using JetBrains.Annotations;
@ -26,7 +28,13 @@ namespace Avalonia.Controls
/// It handles scheduling layout, styling and rendering as well as
/// tracking the widget's <see cref="ClientSize"/>.
/// </remarks>
public abstract class TopLevel : ContentControl, IInputRoot, ILayoutRoot, IRenderRoot, ICloseable, IStyleRoot
public abstract class TopLevel : ContentControl,
IInputRoot,
ILayoutRoot,
IRenderRoot,
ICloseable,
IStyleRoot,
IWeakSubscriber<ResourcesChangedEventArgs>
{
/// <summary>
/// Defines the <see cref="ClientSize"/> property.
@ -100,7 +108,6 @@ namespace Avalonia.Controls
impl.Resized = HandleResized;
impl.ScalingChanged = HandleScalingChanged;
_keyboardNavigationHandler?.SetOwner(this);
_accessKeyHandler?.SetOwner(this);
styler?.ApplyStyles(this);
@ -116,6 +123,14 @@ namespace Avalonia.Controls
{
_applicationLifecycle.OnExit += OnApplicationExiting;
}
if (((IStyleHost)this).StylingParent is IResourceProvider applicationResources)
{
WeakSubscriptionManager.Subscribe(
applicationResources,
nameof(IResourceProvider.ResourcesChanged),
this);
}
}
/// <summary>
@ -165,6 +180,11 @@ namespace Avalonia.Controls
/// <inheritdoc/>
IMouseDevice IInputRoot.MouseDevice => PlatformImpl?.MouseDevice;
void IWeakSubscriber<ResourcesChangedEventArgs>.OnEvent(object sender, ResourcesChangedEventArgs e)
{
((ILogical)this).NotifyResourcesChanged(e);
}
/// <summary>
/// Gets or sets a value indicating whether access keys are shown in the window.
/// </summary>

24
src/Avalonia.Controls/Window.cs

@ -61,11 +61,17 @@ namespace Avalonia.Controls
AvaloniaProperty.Register<Window, SizeToContent>(nameof(SizeToContent));
/// <summary>
/// Enables of disables system window decorations (title bar, buttons, etc)
/// Enables or disables system window decorations (title bar, buttons, etc)
/// </summary>
public static readonly StyledProperty<bool> HasSystemDecorationsProperty =
AvaloniaProperty.Register<Window, bool>(nameof(HasSystemDecorations), true);
/// <summary>
/// Enables or disables the taskbar icon
/// </summary>
public static readonly StyledProperty<bool> ShowInTaskbarProperty =
AvaloniaProperty.Register<Window, bool>(nameof(ShowInTaskbar), true);
/// <summary>
/// Defines the <see cref="Title"/> property.
/// </summary>
@ -92,6 +98,8 @@ namespace Avalonia.Controls
HasSystemDecorationsProperty.Changed.AddClassHandler<Window>(
(s, e) => s.PlatformImpl?.SetSystemDecorations((bool) e.NewValue));
ShowInTaskbarProperty.Changed.AddClassHandler<Window>((w, e) => w.PlatformImpl?.ShowTaskbarIcon((bool)e.NewValue));
IconProperty.Changed.AddClassHandler<Window>((s, e) => s.PlatformImpl?.SetIcon(((WindowIcon)e.NewValue).PlatformImpl));
}
@ -152,7 +160,7 @@ namespace Avalonia.Controls
}
/// <summary>
/// Enables of disables system window decorations (title bar, buttons, etc)
/// Enables or disables system window decorations (title bar, buttons, etc)
/// </summary>
///
public bool HasSystemDecorations
@ -160,6 +168,16 @@ namespace Avalonia.Controls
get { return GetValue(HasSystemDecorationsProperty); }
set { SetValue(HasSystemDecorationsProperty, value); }
}
/// <summary>
/// Enables or disables the taskbar icon
/// </summary>
///
public bool ShowInTaskbar
{
get { return GetValue(ShowInTaskbarProperty); }
set { SetValue(ShowInTaskbarProperty, value); }
}
/// <summary>
/// Gets or sets the minimized/maximized state of the window.

2
src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.3</TargetFramework>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">

2
src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.3</TargetFramework>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">

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

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp1.0</TargetFramework>
<TargetFramework>netcoreapp2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
<PropertyGroup>

94
src/Avalonia.DotNetFrameworkRuntime/Avalonia.DotNetFrameworkRuntime.csproj

@ -1,75 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{4A1ABB09-9047-4BD5-A4AD-A055E52C5EE0}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Avalonia.DotNetFrameworkRuntime</RootNamespace>
<AssemblyName>Avalonia.DotNetFrameworkRuntime</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>TRACE;DEBUG;FULLDOTNET</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Debug\Avalonia.DotNetFrameworkRuntime.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE;FULLDOTNET</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Release\Avalonia.DotNetFrameworkRuntime.xml</DocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\Shared\SharedAssemblyInfo.cs">
<Link>Properties\SharedAssemblyInfo.cs</Link>
</Compile>
<Compile Include="AppBuilder.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RuntimeInfo.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Avalonia.Base\Avalonia.Base.csproj">
<Project>{B09B78D8-9B26-48B0-9149-D64A2F120F3F}</Project>
<Name>Avalonia.Base</Name>
</ProjectReference>
<ProjectReference Include="..\Avalonia.Controls\Avalonia.Controls.csproj">
<Project>{D2221C82-4A25-4583-9B43-D791E3F6820C}</Project>
<Name>Avalonia.Controls</Name>
</ProjectReference>
<ProjectReference Include="..\Avalonia.Visuals\Avalonia.Visuals.csproj">
<Project>{eb582467-6abb-43a1-b052-e981ba910e3a}</Project>
<Name>Avalonia.Visuals</Name>
</ProjectReference>
<ProjectReference Include="..\Avalonia.Styling\Avalonia.Styling.csproj">
<Project>{f1baa01a-f176-4c6a-b39d-5b40bb1b148f}</Project>
<Name>Avalonia.Styling</Name>
</ProjectReference>
</ItemGroup>
<Import Project="..\Shared\PlatformSupport\PlatformSupport.projitems" Label="Shared" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\..\build\Rx.props" />
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net461</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<DocumentationFile>bin\$(Configuration)\Avalonia.DotNetFrameworkRuntime.xml</DocumentationFile>
<DefineConstants>$(DefineConstants);FULLDOTNET</DefineConstants>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Shared\SharedAssemblyInfo.cs">
<Link>Properties\SharedAssemblyInfo.cs</Link>
</Compile>
<ProjectReference Include="..\Avalonia.Base\Avalonia.Base.csproj" />
<ProjectReference Include="..\Avalonia.Controls\Avalonia.Controls.csproj" />
<ProjectReference Include="..\Avalonia.Visuals\Avalonia.Visuals.csproj" />
<ProjectReference Include="..\Avalonia.Styling\Avalonia.Styling.csproj" />
</ItemGroup>
<Import Project="..\Shared\PlatformSupport\PlatformSupport.projitems" Label="Shared" />
<Import Project="..\..\build\Rx.props" />
</Project>

15
src/Avalonia.DotNetFrameworkRuntime/Properties/AssemblyInfo.cs

@ -1,15 +0,0 @@
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Avalonia.DotNetFrameworkRuntime")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("4a1abb09-9047-4bd5-a4ad-a055e52c5ee0")]

2
src/Avalonia.HtmlRenderer/Avalonia.HtmlRenderer.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.3</TargetFramework>
<TargetFramework>netstandard2.0</TargetFramework>
<EnableDefaultCompileItems>False</EnableDefaultCompileItems>
<EnableDefaultItems>False</EnableDefaultItems>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>

12
src/Avalonia.HtmlRenderer/Compat/Api.cs

@ -4,18 +4,6 @@ using System.Text;
namespace System.Net
{
internal class AsyncCompletedEventArgs
{
public object UserState { get; set; }
public Exception Error { get; set; }
public bool Cancelled { get; set; }
public AsyncCompletedEventArgs(Exception error, bool cancelled, object userState)
{
}
}
class WebException : Exception
{
public object Response { get; set; }

2
src/Avalonia.Input/Avalonia.Input.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.3</TargetFramework>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">

2
src/Avalonia.Interactivity/Avalonia.Interactivity.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.3</TargetFramework>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">

2
src/Avalonia.Layout/Avalonia.Layout.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.3</TargetFramework>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">

2
src/Avalonia.Logging.Serilog/Avalonia.Logging.Serilog.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.3</TargetFramework>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">

2
src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.3</TargetFramework>
<TargetFramework>netstandard2.0</TargetFramework>
<EnableDefaultCompileItems>False</EnableDefaultCompileItems>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>

3
src/Avalonia.Styling/Avalonia.Styling.csproj

@ -1,7 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.3</TargetFramework>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<RootNamespace>Avalonia</RootNamespace>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DebugSymbols>true</DebugSymbols>

19
src/Avalonia.Styling/Controls/IResourceDictionary.cs

@ -0,0 +1,19 @@
// 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.Collections.Generic;
namespace Avalonia.Controls
{
/// <summary>
/// An indexed dictionary of resources.
/// </summary>
public interface IResourceDictionary : IResourceProvider, IDictionary<object, object>
{
/// <summary>
/// Gets a collection of child resource dictionaries.
/// </summary>
IList<IResourceProvider> MergedDictionaries { get; }
}
}

15
src/Avalonia.Styling/Controls/IResourceNode.cs

@ -0,0 +1,15 @@
using System;
namespace Avalonia.Controls
{
/// <summary>
/// Represents resource provider in a tree.
/// </summary>
public interface IResourceNode : IResourceProvider
{
/// <summary>
/// Gets the parent resource node, if any.
/// </summary>
IResourceNode ResourceParent { get; }
}
}

33
src/Avalonia.Styling/Controls/IResourceProvider.cs

@ -0,0 +1,33 @@
using System;
namespace Avalonia.Controls
{
/// <summary>
/// Represents an object that can be queried for resources.
/// </summary>
public interface IResourceProvider
{
/// <summary>
/// Raised when resources in the provider are changed.
/// </summary>
event EventHandler<ResourcesChangedEventArgs> ResourcesChanged;
/// <summary>
/// Gets a value indicating whether the element has resources.
/// </summary>
bool HasResources { get; }
/// <summary>
/// Tries to find a resource within the provider.
/// </summary>
/// <param name="key">The resource key.</param>
/// <param name="value">
/// When this method returns, contains the value associated with the specified key,
/// if the key is found; otherwise, null.
/// </param>
/// <returns>
/// True if the resource if found, otherwise false.
/// </returns>
bool TryGetResource(string key, out object value);
}
}

101
src/Avalonia.Styling/Controls/ResourceDictionary.cs

@ -0,0 +1,101 @@
// 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.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using Avalonia.Collections;
namespace Avalonia.Controls
{
/// <summary>
/// An indexed dictionary of resources.
/// </summary>
public class ResourceDictionary : AvaloniaDictionary<object, object>, IResourceDictionary
{
private AvaloniaList<IResourceProvider> _mergedDictionaries;
/// <summary>
/// Initializes a new instance of the <see cref="ResourceDictionary"/> class.
/// </summary>
public ResourceDictionary()
{
CollectionChanged += OnCollectionChanged;
}
/// <inheritdoc/>
public event EventHandler<ResourcesChangedEventArgs> ResourcesChanged;
/// <inheritdoc/>
public IList<IResourceProvider> MergedDictionaries
{
get
{
if (_mergedDictionaries == null)
{
_mergedDictionaries = new AvaloniaList<IResourceProvider>();
_mergedDictionaries.ResetBehavior = ResetBehavior.Remove;
_mergedDictionaries.ForEachItem(
x =>
{
if (x.HasResources)
{
OnResourcesChanged();
}
x.ResourcesChanged += MergedDictionaryResourcesChanged;
},
x =>
{
if (x.HasResources)
{
OnResourcesChanged();
}
x.ResourcesChanged -= MergedDictionaryResourcesChanged;
},
() => { });
}
return _mergedDictionaries;
}
}
/// <inheritdoc/>
bool IResourceProvider.HasResources
{
get => Count > 0 || (_mergedDictionaries?.Any(x => x.HasResources) ?? false);
}
/// <inheritdoc/>
public bool TryGetResource(string key, out object value)
{
if (TryGetValue(key, out value))
{
return true;
}
if (_mergedDictionaries != null)
{
for (var i = _mergedDictionaries.Count - 1; i >= 0; --i)
{
if (_mergedDictionaries[i].TryGetResource(key, out value))
{
return true;
}
}
}
return false;
}
private void OnResourcesChanged()
{
ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs());
}
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) => OnResourcesChanged();
private void MergedDictionaryResourcesChanged(object sender, ResourcesChangedEventArgs e) => OnResourcesChanged();
}
}

65
src/Avalonia.Styling/Controls/ResourceProviderExtensions.cs

@ -0,0 +1,65 @@
using System;
using System.Reactive;
using System.Reactive.Linq;
namespace Avalonia.Controls
{
public static class ResourceProviderExtensions
{
/// <summary>
/// Finds the specified resource by searching up the logical tree and then global styles.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="key">The resource key.</param>
/// <returns>The resource, or <see cref="AvaloniaProperty.UnsetValue"/> if not found.</returns>
public static object FindResource(this IResourceNode control, string key)
{
if (control.TryFindResource(key, out var value))
{
return value;
}
return AvaloniaProperty.UnsetValue;
}
/// <summary>
/// Tries to the specified resource by searching up the logical tree and then global styles.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="key">The resource key.</param>
/// <param name="value">On return, contains the resource if found, otherwise null.</param>
/// <returns>True if the resource was found; otherwise false.</returns>
public static bool TryFindResource(this IResourceNode control, string key, out object value)
{
Contract.Requires<ArgumentNullException>(control != null);
Contract.Requires<ArgumentNullException>(key != null);
var current = control;
while (current != null)
{
if (current is IResourceNode host)
{
if (host.TryGetResource(key, out value))
{
return true;
}
}
current = current.ResourceParent;
}
value = null;
return false;
}
public static IObservable<object> GetResourceObservable(this IResourceNode target, string key)
{
return Observable.FromEventPattern<ResourcesChangedEventArgs>(
x => target.ResourcesChanged += x,
x => target.ResourcesChanged -= x)
.StartWith((EventPattern<ResourcesChangedEventArgs>)null)
.Select(x => target.FindResource(key));
}
}
}

11
src/Avalonia.Styling/Controls/ResourcesChangedEventArgs.cs

@ -0,0 +1,11 @@
// 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;
namespace Avalonia.Controls
{
public class ResourcesChangedEventArgs : EventArgs
{
}
}

11
src/Avalonia.Styling/LogicalTree/ILogical.cs

@ -3,6 +3,7 @@
using System;
using Avalonia.Collections;
using Avalonia.Controls;
namespace Avalonia.LogicalTree
{
@ -55,5 +56,15 @@ namespace Avalonia.LogicalTree
/// this method yourself.
/// </remarks>
void NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e);
/// <summary>
/// Notifies the control that a change has been made to resources that apply to it.
/// </summary>
/// <param name="e">The event args.</param>
/// <remarks>
/// This method will be called automatically by the framework, you should not need to call
/// this method yourself.
/// </remarks>
void NotifyResourcesChanged(ResourcesChangedEventArgs e);
}
}

2
src/Avalonia.Styling/Properties/AssemblyInfo.cs

@ -6,5 +6,7 @@ using System.Runtime.CompilerServices;
using Avalonia.Metadata;
[assembly: AssemblyTitle("Avalonia.Styling")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.LogicalTree")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Styling")]
[assembly: InternalsVisibleTo("Avalonia.Styling.UnitTests")]

32
src/Avalonia.Styling/Styling/ISetStyleParent.cs

@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Text;
using Avalonia.Controls;
namespace Avalonia.Styling
{
/// <summary>
/// Defines an interface through which a <see cref="Style"/>'s parent can be set.
/// </summary>
/// <remarks>
/// You should not usually need to use this interface - it is for internal use only.
/// </remarks>
public interface ISetStyleParent : IStyle
{
/// <summary>
/// Sets the style parent.
/// </summary>
/// <param name="parent">The parent.</param>
void SetParent(IResourceNode parent);
/// <summary>
/// Notifies the style that a change has been made to resources that apply to it.
/// </summary>
/// <param name="e">The event args.</param>
/// <remarks>
/// This method will be called automatically by the framework, you should not need to call
/// this method yourself.
/// </remarks>
void NotifyResourcesChanged(ResourcesChangedEventArgs e);
}
}

13
src/Avalonia.Styling/Styling/IStyle.cs

@ -1,12 +1,14 @@
// 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 Avalonia.Controls;
namespace Avalonia.Styling
{
/// <summary>
/// Defines the interface for styles.
/// </summary>
public interface IStyle
public interface IStyle : IResourceNode
{
/// <summary>
/// Attaches the style to a control if the style's selector matches.
@ -16,14 +18,5 @@ namespace Avalonia.Styling
/// The control that contains this style. May be null.
/// </param>
void Attach(IStyleable control, IStyleHost container);
/// <summary>
/// Tries to find a named resource within the style.
/// </summary>
/// <param name="name">The resource name.</param>
/// <returns>
/// The resource if found, otherwise <see cref="AvaloniaProperty.UnsetValue"/>.
/// </returns>
object FindResource(string name);
}
}

92
src/Avalonia.Styling/Styling/Style.cs

@ -3,7 +3,9 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Reactive.Linq;
using Avalonia.Controls;
using Avalonia.Metadata;
namespace Avalonia.Styling
@ -11,12 +13,12 @@ namespace Avalonia.Styling
/// <summary>
/// Defines a style.
/// </summary>
public class Style : IStyle
public class Style : IStyle, ISetStyleParent
{
private static Dictionary<IStyleable, List<IDisposable>> _applied =
new Dictionary<IStyleable, List<IDisposable>>();
private StyleResources _resources;
private IResourceNode _parent;
private IResourceDictionary _resources;
/// <summary>
/// Initializes a new instance of the <see cref="Style"/> class.
@ -34,33 +36,33 @@ namespace Avalonia.Styling
Selector = selector(null);
}
/// <inheritdoc/>
public event EventHandler<ResourcesChangedEventArgs> ResourcesChanged;
/// <summary>
/// Gets or sets a dictionary of style resources.
/// </summary>
public StyleResources Resources
public IResourceDictionary Resources
{
get
get => _resources ?? (Resources = new ResourceDictionary());
set
{
if (_resources == null)
Contract.Requires<ArgumentNullException>(value != null);
var hadResources = false;
if (_resources != null)
{
_resources = new StyleResources();
hadResources = _resources.Count > 0;
_resources.ResourcesChanged -= ResourceDictionaryChanged;
}
return _resources;
}
_resources = value;
_resources.ResourcesChanged += ResourceDictionaryChanged;
set
{
var resources = Resources;
if (!Equals(resources, value))
if (hadResources || _resources.Count > 0)
{
foreach (var i in value)
{
resources[i.Key] = i.Value;
//resources.Add(i.Key, i.Value);
//(resources as IDictionary<string,object>).Add(i);
}
((ISetStyleParent)this).NotifyResourcesChanged(new ResourcesChangedEventArgs());
}
}
}
@ -76,6 +78,12 @@ namespace Avalonia.Styling
[Content]
public IList<ISetter> Setters { get; set; } = new List<ISetter>();
/// <inheritdoc/>
IResourceNode IResourceNode.ResourceParent => _parent;
/// <inheritdoc/>
bool IResourceProvider.HasResources => _resources?.Count > 0;
/// <summary>
/// Attaches the style to a control if the style's selector matches.
/// </summary>
@ -112,25 +120,11 @@ namespace Avalonia.Styling
}
}
/// <summary>
/// Tries to find a named resource within the style.
/// </summary>
/// <param name="name">The resource name.</param>
/// <returns>
/// The resource if found, otherwise <see cref="AvaloniaProperty.UnsetValue"/>.
/// </returns>
public object FindResource(string name)
/// <inheritdoc/>
public bool TryGetResource(string key, out object result)
{
object result = null;
if (_resources?.TryGetValue(name, out result) == true)
{
return result;
}
else
{
return AvaloniaProperty.UnsetValue;
}
result = null;
return _resources?.TryGetResource(key, out result) ?? false;
}
/// <summary>
@ -149,6 +143,23 @@ namespace Avalonia.Styling
}
}
/// <inheritdoc/>
void ISetStyleParent.NotifyResourcesChanged(ResourcesChangedEventArgs e)
{
ResourcesChanged?.Invoke(this, e);
}
/// <inheritdoc/>
void ISetStyleParent.SetParent(IResourceNode parent)
{
if (_parent != null && parent != null)
{
throw new InvalidOperationException("The Style already has a parent.");
}
_parent = parent;
}
private static List<IDisposable> GetSubscriptions(IStyleable control)
{
List<IDisposable> subscriptions;
@ -179,5 +190,10 @@ namespace Avalonia.Styling
_applied.Remove(control);
}
private void ResourceDictionaryChanged(object sender, ResourcesChangedEventArgs e)
{
ResourcesChanged?.Invoke(this, e);
}
}
}

42
src/Avalonia.Styling/Styling/StyleExtensions.cs

@ -1,42 +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;
namespace Avalonia.Styling
{
public static class StyleExtensions
{
/// <summary>
/// Tries to find a named style resource.
/// </summary>
/// <param name="control">The control from which to find the resource.</param>
/// <param name="name">The resource name.</param>
/// <returns>
/// The resource if found, otherwise <see cref="AvaloniaProperty.UnsetValue"/>.
/// </returns>
public static object FindStyleResource(this IStyleHost control, string name)
{
Contract.Requires<ArgumentNullException>(control != null);
Contract.Requires<ArgumentNullException>(name != null);
Contract.Requires<ArgumentException>(!string.IsNullOrWhiteSpace(name));
while (control != null)
{
if (control.IsStylesInitialized)
{
var result = control.Styles.FindResource(name);
if (result != AvaloniaProperty.UnsetValue)
{
return result;
}
}
control = control.StylingParent;
}
return AvaloniaProperty.UnsetValue;
}
}
}

90
src/Avalonia.Styling/Styling/StyleResources.cs

@ -1,90 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace Avalonia.Styling
{
/// <summary>
/// Holds resources for a <see cref="Style"/>.
/// </summary>
public class StyleResources : IDictionary<string, object>, IDictionary
{
private Dictionary<string, object> _inner = new Dictionary<string, object>();
public object this[string key]
{
get { return _inner[key]; }
set { _inner[key] = value; }
}
public int Count => _inner.Count;
ICollection<string> IDictionary<string, object>.Keys => _inner.Keys;
ICollection<object> IDictionary<string, object>.Values => _inner.Values;
bool ICollection<KeyValuePair<string, object>>.IsReadOnly => false;
object IDictionary.this[object key]
{
get { return ((IDictionary)_inner)[key]; }
set { ((IDictionary)_inner)[key] = value; }
}
ICollection IDictionary.Keys => _inner.Keys;
ICollection IDictionary.Values => _inner.Values;
bool ICollection.IsSynchronized => false;
object ICollection.SyncRoot => ((IDictionary)_inner).SyncRoot;
bool IDictionary.IsFixedSize => false;
bool IDictionary.IsReadOnly => false;
public void Add(string key, object value) => _inner.Add(key, value);
public void Clear() => _inner.Clear();
public bool ContainsKey(string key) => _inner.ContainsKey(key);
public bool Remove(string key) => _inner.Remove(key);
public IEnumerator<KeyValuePair<string, object>> GetEnumerator() => _inner.GetEnumerator();
public bool TryGetValue(string key, out object value) => _inner.TryGetValue(key, out value);
bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item)
{
return ((IDictionary<string, object>)_inner).Contains(item);
}
void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item)
{
((IDictionary<string, object>)_inner).Add(item);
}
void ICollection<KeyValuePair<string, object>>.CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
{
((IDictionary<string, object>)_inner).CopyTo(array, arrayIndex);
}
bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item)
{
return ((IDictionary<string, object>)_inner).Remove(item);
}
void ICollection.CopyTo(Array array, int index) => ((IDictionary)_inner).CopyTo(array, index);
IEnumerator IEnumerable.GetEnumerator() => _inner.GetEnumerator();
IDictionaryEnumerator IDictionary.GetEnumerator() => ((IDictionary)_inner).GetEnumerator();
void IDictionary.Add(object key, object value) => ((IDictionary)_inner).Add(key, value);
bool IDictionary.Contains(object key) => ((IDictionary)_inner).Contains(key);
void IDictionary.Remove(object key) => ((IDictionary)_inner).Remove(key);
}
}

149
src/Avalonia.Styling/Styling/Styles.cs

@ -1,16 +1,95 @@
// 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.Linq;
using Avalonia.Collections;
using Avalonia.Controls;
namespace Avalonia.Styling
{
/// <summary>
/// A style that consists of a number of child styles.
/// </summary>
public class Styles : AvaloniaList<IStyle>, IStyle
public class Styles : AvaloniaList<IStyle>, IStyle, ISetStyleParent
{
private IResourceNode _parent;
private IResourceDictionary _resources;
public Styles()
{
ResetBehavior = ResetBehavior.Remove;
this.ForEachItem(
x =>
{
if (x.ResourceParent == null && x is ISetStyleParent setParent)
{
setParent.SetParent(this);
setParent.NotifyResourcesChanged(new ResourcesChangedEventArgs());
}
if (x.HasResources)
{
ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs());
}
x.ResourcesChanged += SubResourceChanged;
},
x =>
{
if (x.ResourceParent == this && x is ISetStyleParent setParent)
{
setParent.SetParent(null);
setParent.NotifyResourcesChanged(new ResourcesChangedEventArgs());
}
if (x.HasResources)
{
ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs());
}
x.ResourcesChanged -= SubResourceChanged;
},
() => { });
}
/// <inheritdoc/>
public event EventHandler<ResourcesChangedEventArgs> ResourcesChanged;
/// <inheritdoc/>
public bool HasResources => _resources?.Count > 0 || this.Any(x => x.HasResources);
/// <summary>
/// Gets or sets a dictionary of style resources.
/// </summary>
public IResourceDictionary Resources
{
get => _resources ?? (Resources = new ResourceDictionary());
set
{
Contract.Requires<ArgumentNullException>(value != null);
var hadResources = false;
if (_resources != null)
{
hadResources = _resources.Count > 0;
_resources.ResourcesChanged -= ResourceDictionaryChanged;
}
_resources = value;
_resources.ResourcesChanged += ResourceDictionaryChanged;
if (hadResources || _resources.Count > 0)
{
((ISetStyleParent)this).NotifyResourcesChanged(new ResourcesChangedEventArgs());
}
}
}
/// <inheritdoc/>
IResourceNode IResourceNode.ResourceParent => _parent;
/// <summary>
/// Attaches the style to a control if the style's selector matches.
/// </summary>
@ -26,26 +105,68 @@ namespace Avalonia.Styling
}
}
/// <summary>
/// Tries to find a named resource within the style.
/// </summary>
/// <param name="name">The resource name.</param>
/// <returns>
/// The resource if found, otherwise <see cref="AvaloniaProperty.UnsetValue"/>.
/// </returns>
public object FindResource(string name)
/// <inheritdoc/>
public bool TryGetResource(string key, out object value)
{
if (_resources != null && _resources.TryGetValue(key, out value))
{
return true;
}
for (var i = Count - 1; i >= 0; --i)
{
if (this[i].TryGetResource(key, out value))
{
return true;
}
}
value = null;
return false;
}
/// <inheritdoc/>
void ISetStyleParent.SetParent(IResourceNode parent)
{
if (_parent != null && parent != null)
{
throw new InvalidOperationException("The Style already has a parent.");
}
_parent = parent;
}
/// <inheritdoc/>
void ISetStyleParent.NotifyResourcesChanged(ResourcesChangedEventArgs e)
{
ResourcesChanged?.Invoke(this, e);
}
private void ResourceDictionaryChanged(object sender, ResourcesChangedEventArgs e)
{
foreach (var style in this.Reverse())
foreach (var child in this)
{
var result = style.FindResource(name);
(child as ISetStyleParent)?.NotifyResourcesChanged(e);
}
ResourcesChanged?.Invoke(this, e);
}
if (result != AvaloniaProperty.UnsetValue)
private void SubResourceChanged(object sender, ResourcesChangedEventArgs e)
{
var foundSource = false;
foreach (var child in this)
{
if (foundSource)
{
return result;
(child as ISetStyleParent)?.NotifyResourcesChanged(e);
}
foundSource |= child == sender;
}
return AvaloniaProperty.UnsetValue;
ResourcesChanged?.Invoke(this, e);
}
}
}

2
src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.3</TargetFramework>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">

2
src/Avalonia.Visuals/Avalonia.Visuals.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.3</TargetFramework>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<RootNamespace>Avalonia</RootNamespace>
</PropertyGroup>

18
src/Gtk/Avalonia.Gtk/WindowImpl.cs

@ -6,11 +6,12 @@ using Gdk;
namespace Avalonia.Gtk
{
using Gtk = global::Gtk;
public class WindowImpl : TopLevelImpl, IWindowImpl
{
private Gtk.Window _window;
private Gtk.Window Window => _window ?? (_window = (Gtk.Window) Widget);
private Gtk.Window Window => _window ?? (_window = (Gtk.Window)Widget);
public WindowImpl(Gtk.WindowType type) : base(new PlatformHandleAwareWindow(type))
{
Init();
@ -29,8 +30,10 @@ namespace Avalonia.Gtk
_lastClientSize = ClientSize;
_lastPosition = Position;
}
private Size _lastClientSize;
private Point _lastPosition;
void OnConfigureEvent(object o, Gtk.ConfigureEventArgs args)
{
var evnt = args.Event;
@ -44,7 +47,7 @@ namespace Avalonia.Gtk
}
var newPosition = new Point(evnt.X, evnt.Y);
if (newPosition != _lastPosition)
{
PositionChanged(newPosition);
@ -107,10 +110,7 @@ namespace Avalonia.Gtk
Window.GetPosition(out x, out y);
return new Point(x, y);
}
set
{
Window.Move((int)value.X, (int)value.Y);
}
set { Window.Move((int)value.X, (int)value.Y); }
}
public IDisposable ShowDialog()
@ -127,5 +127,7 @@ namespace Avalonia.Gtk
{
Window.Icon = ((IconImpl)icon).Pixbuf;
}
public void ShowTaskbarIcon(bool value) => Window.SkipTaskbarHint = !value;
}
}
}

2
src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.3</TargetFramework>
<TargetFramework>netstandard2.0</TargetFramework>
<EnableDefaultCompileItems>False</EnableDefaultCompileItems>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>

49
src/Gtk/Avalonia.Gtk3/Interop/Native.cs

@ -38,6 +38,9 @@ namespace Avalonia.Gtk3.Interop
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_widget_hide(GtkWidget gtkWidget);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_widget_show(GtkWidget gtkWidget);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_window_set_icon(GtkWindow window, Pixbuf pixbuf);
@ -56,14 +59,17 @@ namespace Avalonia.Gtk3.Interop
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate IntPtr gtk_widget_get_screen(GtkWidget gtkWidget);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate IntPtr gtk_widget_set_double_buffered(GtkWidget gtkWidget, bool value);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate IntPtr gtk_widget_set_events(GtkWidget gtkWidget, uint flags);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
public delegate int gdk_screen_get_height(IntPtr screen);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
public delegate int gdk_screen_get_width(IntPtr screen);
@ -72,10 +78,10 @@ namespace Avalonia.Gtk3.Interop
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
public delegate int gdk_window_get_origin(IntPtr gdkWindow, out int x, out int y);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
public delegate void gdk_window_resize(IntPtr gtkWindow, int width, int height);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_widget_realize(GtkWidget gtkWidget);
@ -86,33 +92,51 @@ namespace Avalonia.Gtk3.Interop
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_window_set_decorated(GtkWindow gtkWindow, bool decorated);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_window_set_skip_taskbar_hint(GtkWindow gtkWindow, bool setting);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate bool gtk_window_get_skip_taskbar_hint(GtkWindow gtkWindow);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_window_set_skip_pager_hint(GtkWindow gtkWindow, bool setting);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate bool gtk_window_get_skip_pager_hint(GtkWindow gtkWindow);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_window_get_size(GtkWindow gtkWindow, out int width, out int height);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_window_resize(GtkWindow gtkWindow, int width, int height);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_widget_set_size_request(GtkWidget widget, int width, int height);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_window_set_default_size(GtkWindow gtkWindow, int width, int height);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_window_get_position(GtkWindow gtkWindow, out int x, out int y);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_window_move(GtkWindow gtkWindow, int x, int y);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate GtkFileChooser gtk_file_chooser_dialog_new(Utf8Buffer title, GtkWindow parent, GtkFileChooserAction action, IntPtr ignore);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public unsafe delegate GSList* gtk_file_chooser_get_filenames(GtkFileChooser chooser);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_file_chooser_set_select_multiple(GtkFileChooser chooser, bool allow);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_file_chooser_set_filename(GtkFileChooser chooser, Utf8Buffer file);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_dialog_add_button(GtkDialog raw, Utf8Buffer button_text, GtkResponseType response_id);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
public delegate CairoSurface cairo_image_surface_create(int format, int width, int height);
@ -206,7 +230,6 @@ namespace Avalonia.Gtk3.Interop
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_clipboard_clear(IntPtr clipboard);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.GdkPixBuf)]
public delegate IntPtr gdk_pixbuf_new_from_file(Utf8Buffer filename, out IntPtr error);
@ -229,21 +252,27 @@ namespace Avalonia.Gtk3.Interop
public delegate bool gdk_pixbuf_save_to_bufferv(Pixbuf pixbuf, out IntPtr buffer, out IntPtr buffer_size,
Utf8Buffer type, IntPtr option_keys, IntPtr option_values, out IntPtr error);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gobject)]
public delegate void g_object_unref(IntPtr instance);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gobject)]
public delegate void g_object_ref(GObject instance);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gobject)]
public delegate ulong g_signal_connect_object(GObject instance, Utf8Buffer signal, IntPtr handler, IntPtr userData, int flags);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gobject)]
public delegate ulong g_signal_handler_disconnect(GObject instance, ulong connectionId);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Glib)]
public delegate ulong g_timeout_add(uint interval, timeout_callback callback, IntPtr data);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Glib)]
public delegate ulong g_free(IntPtr data);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Glib)]
public unsafe delegate void g_slist_free(GSList* data);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gio)]
public delegate GInputStream g_memory_input_stream_new_from_data(IntPtr ptr, IntPtr len, IntPtr destroyCallback);
@ -271,6 +300,10 @@ namespace Avalonia.Gtk3.Interop
public static D.gtk_window_set_decorated GtkWindowSetDecorated;
public static D.gtk_window_set_skip_taskbar_hint GtkWindowSetSkipTaskbarHint;
public static D.gtk_window_get_skip_taskbar_hint GtkWindowGetSkipTaskbarHint;
public static D.gtk_window_set_skip_pager_hint GtkWindowSetSkipPagerHint;
public static D.gtk_window_get_skip_pager_hint GtkWindowGetSkipPagerHint;
public static D.gtk_window_set_title GtkWindowSetTitle;
public static D.gtk_application_new GtkApplicationNew;
public static D.gtk_main_iteration GtkMainIteration;
@ -280,6 +313,7 @@ namespace Avalonia.Gtk3.Interop
public static D.gtk_init GtkInit;
public static D.gtk_window_present GtkWindowPresent;
public static D.gtk_widget_hide GtkWidgetHide;
public static D.gtk_widget_show GtkWidgetShow;
public static D.gdk_get_native_handle GetNativeGdkWindowHandle;
public static D.gtk_widget_get_window GtkWidgetGetWindow;
public static D.gtk_widget_get_scale_factor GtkWidgetGetScaleFactor;
@ -314,8 +348,7 @@ namespace Avalonia.Gtk3.Interop
public static D.gtk_clipboard_request_text GtkClipboardRequestText;
public static D.gtk_clipboard_set_text GtkClipboardSetText;
public static D.gtk_clipboard_clear GtkClipboardRequestClear;
public static D.gtk_im_multicontext_new GtkImMulticontextNew;
public static D.gtk_im_context_filter_keypress GtkImContextFilterKeypress;
public static D.gtk_im_context_set_client_window GtkImContextSetClientWindow;

16
src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs

@ -104,14 +104,16 @@ namespace Avalonia.Gtk3.Interop
var path = Custom?.Lookup(dll);
if (path == null && Custom?.BasePath != null)
path = Path.Combine(Custom.BasePath, name);
try
{
return loader.LoadLibrary(path);
}
catch (Exception e)
if (path != null)
{
exceptions.Add(e);
try
{
return loader.LoadLibrary(path);
}
catch (Exception e)
{
exceptions.Add(e);
}
}
throw new AggregateException("Unable to load " + dll, exceptions);
}

4
src/Gtk/Avalonia.Gtk3/WindowImpl.cs

@ -2,6 +2,7 @@
using Avalonia.Controls;
using Avalonia.Gtk3.Interop;
using Avalonia.Platform;
using System.Runtime.InteropServices;
namespace Avalonia.Gtk3
{
@ -59,6 +60,9 @@ namespace Avalonia.Gtk3
//Why do we even have that?
}
public void ShowTaskbarIcon(bool value) => Native.GtkWindowSetSkipTaskbarHint(GtkWidget, !value);
class EmptyDisposable : IDisposable
{
public void Dispose()

2
src/Linux/Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.3</TargetFramework>
<TargetFramework>netstandard2.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>

6
src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.3</TargetFramework>
<TargetFramework>netstandard2.0</TargetFramework>
<EnableDefaultCompileItems>False</EnableDefaultCompileItems>
<EnableDefaultItems>false</EnableDefaultItems>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
@ -34,6 +34,9 @@
<Compile Include="Converters\MatrixTypeConverter.cs" />
<Compile Include="Converters\RectTypeConverter.cs" />
<Compile Include="Converters\SetterValueTypeConverter.cs" />
<Compile Include="Data\ResourceInclude.cs" />
<Compile Include="MarkupExtensions\DynamicResourceExtension.cs" />
<Compile Include="MarkupExtensions\StaticResourceExtension.cs" />
<Compile Include="MarkupExtensions\StyleIncludeExtension.cs" />
<Compile Include="PortableXaml\AvaloniaXamlContext.cs" />
<Compile Include="PortableXaml\XamlBinding.cs" />
@ -94,6 +97,7 @@
<Compile Include="Templates\TemplateLoader.cs" />
<Compile Include="Templates\TreeDataTemplate.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>
<ItemGroup>
<ProjectReference Include="..\..\Avalonia.Animation\Avalonia.Animation.csproj" />

85
src/Markup/Avalonia.Markup.Xaml/Data/DelayedBinding.cs

@ -3,9 +3,11 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Logging;
namespace Avalonia.Markup.Xaml.Data
{
@ -50,7 +52,36 @@ namespace Avalonia.Markup.Xaml.Data
target.Initialized += ApplyBindings;
}
bindings.Add(new Entry(binding, property));
bindings.Add(new BindingEntry(property, binding));
}
}
/// <summary>
/// Adds a delayed value to a control.
/// </summary>
/// <param name="target">The control.</param>
/// <param name="property">The property on the control to bind to.</param>
/// <param name="value">A function which returns the value.</param>
public static void Add(IControl target, PropertyInfo property, Func<IControl, object> value)
{
if (target.IsInitialized)
{
property.SetValue(target, value(target));
}
else
{
List<Entry> bindings;
if (!_entries.TryGetValue(target, out bindings))
{
bindings = new List<Entry>();
_entries.Add(target, bindings);
// TODO: Make this a weak event listener.
target.Initialized += ApplyBindings;
}
bindings.Add(new ClrPropertyValueEntry(property, value));
}
}
@ -60,13 +91,13 @@ namespace Avalonia.Markup.Xaml.Data
/// <param name="control">The control.</param>
public static void ApplyBindings(IControl control)
{
List<Entry> bindings;
List<Entry> entries;
if (_entries.TryGetValue(control, out bindings))
if (_entries.TryGetValue(control, out entries))
{
foreach (var binding in bindings)
foreach (var entry in entries)
{
control.Bind(binding.Property, binding.Binding);
entry.Apply(control);
}
_entries.Remove(control);
@ -80,9 +111,14 @@ namespace Avalonia.Markup.Xaml.Data
target.Initialized -= ApplyBindings;
}
private class Entry
private abstract class Entry
{
public abstract void Apply(IControl control);
}
private class BindingEntry : Entry
{
public Entry(IBinding binding, AvaloniaProperty property)
public BindingEntry(AvaloniaProperty property, IBinding binding)
{
Binding = binding;
Property = property;
@ -90,6 +126,41 @@ namespace Avalonia.Markup.Xaml.Data
public IBinding Binding { get; }
public AvaloniaProperty Property { get; }
public override void Apply(IControl control)
{
control.Bind(Property, Binding);
}
}
private class ClrPropertyValueEntry : Entry
{
public ClrPropertyValueEntry(PropertyInfo property, Func<IControl, object> value)
{
Property = property;
Value = value;
}
public PropertyInfo Property { get; }
public Func<IControl, object> Value { get; }
public override void Apply(IControl control)
{
try
{
Property.SetValue(control, Value(control));
}
catch (Exception e)
{
Logger.Error(
LogArea.Property,
control,
"Error setting {Property} on {Target}: {Exception}",
Property.Name,
control,
e);
}
}
}
}
}

63
src/Markup/Avalonia.Markup.Xaml/Data/ResourceInclude.cs

@ -0,0 +1,63 @@
using System;
using System.ComponentModel;
using Avalonia.Controls;
using Portable.Xaml.ComponentModel;
using Portable.Xaml.Markup;
namespace Avalonia.Markup.Xaml.Data
{
/// <summary>
/// Loads a resource dictionary from a specified URL.
/// </summary>
public class ResourceInclude : MarkupExtension, IResourceProvider
{
private Uri _baseUri;
private IResourceDictionary _loaded;
public event EventHandler<ResourcesChangedEventArgs> ResourcesChanged;
/// <summary>
/// Gets the loaded resource dictionary.
/// </summary>
public IResourceDictionary Loaded
{
get
{
if (_loaded == null)
{
var loader = new AvaloniaXamlLoader();
_loaded = (IResourceDictionary)loader.Load(Source, _baseUri);
if (_loaded.HasResources)
{
ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs());
}
}
return _loaded;
}
}
/// <summary>
/// Gets or sets the source URL.
/// </summary>
public Uri Source { get; set; }
/// <inhertidoc/>
bool IResourceProvider.HasResources => Loaded.HasResources;
/// <inhertidoc/>
bool IResourceProvider.TryGetResource(string key, out object value)
{
return Loaded.TryGetResource(key, out value);
}
/// <inhertidoc/>
public override object ProvideValue(IServiceProvider serviceProvider)
{
var tdc = (ITypeDescriptorContext)serviceProvider;
_baseUri = tdc?.GetBaseUri();
return this;
}
}
}

7
src/Markup/Avalonia.Markup.Xaml/Data/StyleResourceBinding.cs

@ -46,11 +46,14 @@ namespace Avalonia.Markup.Xaml.Data
if (host != null)
{
resource = host.FindStyleResource(Name);
resource = host.FindResource(Name);
}
else if (style != null)
{
resource = style.FindResource(Name);
if (!style.TryGetResource(Name, out resource))
{
resource = AvaloniaProperty.UnsetValue;
}
}
if (resource != AvaloniaProperty.UnsetValue)

71
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs

@ -0,0 +1,71 @@
// 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.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using Avalonia.Controls;
using Avalonia.Data;
using Portable.Xaml;
using Portable.Xaml.ComponentModel;
using Portable.Xaml.Markup;
namespace Avalonia.Markup.Xaml.MarkupExtensions
{
public class DynamicResourceExtension : MarkupExtension, IBinding
{
private IResourceNode _anchor;
public DynamicResourceExtension()
{
}
public DynamicResourceExtension(string resourceKey)
{
ResourceKey = resourceKey;
}
public string ResourceKey { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
var context = (ITypeDescriptorContext)serviceProvider;
var provideTarget = context.GetService<IProvideValueTarget>();
if (!(provideTarget.TargetObject is IResourceNode))
{
_anchor = GetAnchor<IResourceNode>(context);
}
return this;
}
InstancedBinding IBinding.Initiate(
IAvaloniaObject target,
AvaloniaProperty targetProperty,
object anchor,
bool enableDataValidation)
{
var control = target as IResourceNode ?? _anchor;
if (control != null)
{
return new InstancedBinding(control.GetResourceObservable(ResourceKey));
}
return null;
}
private T GetAnchor<T>(ITypeDescriptorContext context) where T : class
{
var schemaContext = context.GetService<IXamlSchemaContextProvider>().SchemaContext;
var ambientProvider = context.GetService<IAmbientProvider>();
var xamlType = schemaContext.GetXamlType(typeof(T));
// We override XamlType.CanAssignTo in BindingXamlType so the results we get back
// from GetAllAmbientValues aren't necessarily of the correct type.
return ambientProvider.GetAllAmbientValues(xamlType).OfType<T>().FirstOrDefault();
}
}
}

84
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs

@ -0,0 +1,84 @@
// 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.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using Avalonia.Controls;
using Avalonia.Markup.Xaml.Data;
using Portable.Xaml;
using Portable.Xaml.ComponentModel;
using Portable.Xaml.Markup;
namespace Avalonia.Markup.Xaml.MarkupExtensions
{
public class StaticResourceExtension : MarkupExtension
{
public StaticResourceExtension()
{
}
public StaticResourceExtension(string resourceKey)
{
ResourceKey = resourceKey;
}
public string ResourceKey { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
var context = (ITypeDescriptorContext)serviceProvider;
var schemaContext = context.GetService<IXamlSchemaContextProvider>().SchemaContext;
var ambientProvider = context.GetService<IAmbientProvider>();
var resourceProviderType = schemaContext.GetXamlType(typeof(IResourceNode));
var ambientValues = ambientProvider.GetAllAmbientValues(resourceProviderType);
// Look upwards though the ambient context for IResourceProviders which might be able
// to give us the resource.
//
// TODO: If we're in a template then only the ambient values since the root of the
// template wil be included here. We need some way to get hold of the parent ambient
// context and search that. See the test:
//
// StaticResource_Can_Be_Assigned_To_Property_In_ControlTemplate_In_Styles_File
//
foreach (var ambientValue in ambientValues)
{
// We override XamlType.CanAssignTo in BindingXamlType so the results we get back
// from GetAllAmbientValues aren't necessarily of the correct type.
if (ambientValue is IResourceNode resourceProvider)
{
if (resourceProvider is IControl control && control.StylingParent != null)
{
// If we've got to a control that has a StylingParent then it's probably
// a top level control and its StylingParent is pointing to the global
// styles. If this is case just do a FindResource on it.
return control.FindResource(ResourceKey);
}
else if (resourceProvider.TryGetResource(ResourceKey, out var value))
{
return value;
}
}
}
// The resource still hasn't been found, so add a delayed one-time binding.
var provideTarget = context.GetService<IProvideValueTarget>();
if (provideTarget.TargetObject is IControl target &&
provideTarget.TargetProperty is PropertyInfo property)
{
DelayedBinding.Add(target, property, GetValue);
return AvaloniaProperty.UnsetValue;
}
throw new KeyNotFoundException($"Static resource '{ResourceKey}' not found.");
}
private object GetValue(IControl control)
{
return control.FindResource(ResourceKey);
}
}
}

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

@ -1 +1 @@
Subproject commit dfc5affa5d8f4ddf5a7707e3202d5593519de640
Subproject commit eebf9dbb9275ecc48c18ec24f6fbad8cb494857f

41
src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs

@ -3,27 +3,31 @@
using Avalonia.Styling;
using System;
using Avalonia.Controls;
namespace Avalonia.Markup.Xaml.Styling
{
/// <summary>
/// Includes a style from a URL.
/// </summary>
public class StyleInclude : IStyle
public class StyleInclude : IStyle, ISetStyleParent
{
private Uri _baseUri;
private IStyle _loaded;
private IResourceNode _parent;
/// <summary>
/// Initializes a new instance of the <see cref="StyleInclude"/> class.
/// </summary>
/// <param name="baseUri"></param>
public StyleInclude(Uri baseUri)
{
_baseUri = baseUri;
}
/// <inheritdoc/>
public event EventHandler<ResourcesChangedEventArgs> ResourcesChanged;
/// <summary>
/// Gets or sets the source URL.
/// </summary>
@ -40,12 +44,19 @@ namespace Avalonia.Markup.Xaml.Styling
{
var loader = new AvaloniaXamlLoader();
_loaded = (IStyle)loader.Load(Source, _baseUri);
(_loaded as ISetStyleParent)?.SetParent(this);
}
return _loaded;
}
}
/// <inheritdoc/>
bool IResourceProvider.HasResources => Loaded.HasResources;
/// <inheritdoc/>
IResourceNode IResourceNode.ResourceParent => _parent;
/// <inheritdoc/>
public void Attach(IStyleable control, IStyleHost container)
{
@ -55,16 +66,24 @@ namespace Avalonia.Markup.Xaml.Styling
}
}
/// <summary>
/// Tries to find a named resource within the style.
/// </summary>
/// <param name="name">The resource name.</param>
/// <returns>
/// The resource if found, otherwise <see cref="AvaloniaProperty.UnsetValue"/>.
/// </returns>
public object FindResource(string name)
/// <inheritdoc/>
public bool TryGetResource(string key, out object value) => Loaded.TryGetResource(key, out value);
/// <inheritdoc/>
void ISetStyleParent.NotifyResourcesChanged(ResourcesChangedEventArgs e)
{
return Loaded.FindResource(name);
(Loaded as ISetStyleParent)?.NotifyResourcesChanged(e);
}
/// <inheritdoc/>
void ISetStyleParent.SetParent(IResourceNode parent)
{
if (_parent != null && parent != null)
{
throw new InvalidOperationException("The Style already has a parent.");
}
_parent = parent;
}
}
}

2
src/Markup/Avalonia.Markup/Avalonia.Markup.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.3</TargetFramework>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">

2
src/Shared/PlatformSupport/StandardRuntimePlatform.cs

@ -12,7 +12,7 @@ namespace Avalonia.Shared.PlatformSupport
internal partial class StandardRuntimePlatform : IRuntimePlatform
{
#if NETCOREAPP1_0
#if NETCOREAPP2_0
public void PostThreadPoolItem(Action cb) => ThreadPool.QueueUserWorkItem(_ => cb(), null);
#else
public Assembly[] GetLoadedAssemblies() => AppDomain.CurrentDomain.GetAssemblies();

2
src/Skia/Avalonia.Skia/Avalonia.Skia.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.3</TargetFramework>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<RootNamespace>Avalonia.Skia</RootNamespace>
<AssemblyName>Avalonia.Skia</AssemblyName>

2
src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.3</TargetFramework>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
<ItemGroup>

2
src/Windows/Avalonia.Win32.NetStandard/Avalonia.Win32.NetStandard.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.3</TargetFramework>
<TargetFramework>netstandard2.0</TargetFramework>
<EnableDefaultCompileItems>False</EnableDefaultCompileItems>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<RootNamespace>Avalonia.Win32</RootNamespace>

45
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@ -699,10 +699,40 @@ namespace Avalonia.Win32.Interop
public static extern int GetSystemMetrics(SystemMetric smIndex);
[DllImport("user32.dll", SetLastError = true)]
public static extern uint GetWindowLong(IntPtr hWnd, int nIndex);
public static extern uint GetWindowLongPtr(IntPtr hWnd, int nIndex);
[DllImport("user32.dll", SetLastError = true, EntryPoint = "GetWindowLong")]
public static extern uint GetWindowLong32b(IntPtr hWnd, int nIndex);
public static uint GetWindowLong(IntPtr hWnd, int nIndex)
{
if(IntPtr.Size == 4)
{
return GetWindowLong32b(hWnd, nIndex);
}
else
{
return GetWindowLongPtr(hWnd, nIndex);
}
}
[DllImport("user32.dll", SetLastError = true, EntryPoint = "SetWindowLong")]
private static extern uint SetWindowLong32b(IntPtr hWnd, int nIndex, uint value);
[DllImport("user32.dll", SetLastError = true)]
public static extern uint SetWindowLong(IntPtr hWnd, int nIndex, uint value);
private static extern uint SetWindowLongPtr(IntPtr hWnd, int nIndex, uint value);
public static uint SetWindowLong(IntPtr hWnd, int nIndex, uint value)
{
if (IntPtr.Size == 4)
{
return SetWindowLong32b(hWnd, nIndex, value);
}
else
{
return SetWindowLongPtr(hWnd, nIndex, value);
}
}
[DllImport("user32.dll", SetLastError = true)]
public static extern bool GetWindowPlacement(IntPtr hWnd, ref WINDOWPLACEMENT lpwndpl);
@ -811,14 +841,14 @@ namespace Avalonia.Win32.Interop
return SetClassLong64(hWnd, nIndex, dwNewLong);
}
#if !NETSTANDARD && !NETSTANDARD1_3
[ComImport, ClassInterface(ClassInterfaceType.None), TypeLibType(TypeLibTypeFlags.FCanCreate), Guid("DC1C5A9C-E88A-4DDE-A5A1-60F82A20AEF7")]
internal class FileOpenDialogRCW { }
[DllImport("ole32.dll", PreserveSig = true)]
internal static extern int CoCreateInstance(ref Guid clsid,
IntPtr ignore1, int ignore2, ref Guid iid, [MarshalAs(UnmanagedType.IUnknown), Out] out object pUnkOuter);
[DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern int SHCreateItemFromParsingName([MarshalAs(UnmanagedType.LPWStr)] string pszPath, IntPtr pbc, ref Guid riid, [MarshalAs(UnmanagedType.Interface)] out IShellItem ppv);
#endif
[DllImport("user32.dll", SetLastError = true)]
public static extern bool OpenClipboard(IntPtr hWndOwner);
@ -1153,7 +1183,7 @@ namespace Avalonia.Win32.Interop
public int flagsEx;
}
}
#if !NETSTANDARD && !NETSTANDARD1_3
[ComImport(), Guid("42F85136-DB7E-439C-85F1-E4075D135FC8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IFileDialog
{
@ -1253,5 +1283,4 @@ namespace Avalonia.Win32.Interop
uint Compare([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, [In] uint hint, out int piOrder);
}
#endif
}

15
src/Windows/Avalonia.Win32/SystemDialogImpl.cs

@ -100,17 +100,14 @@ namespace Avalonia.Win32
var pofn = &ofn;
// We should save the current directory to restore it later.
#if !NETSTANDARD
var currentDirectory = Environment.CurrentDirectory;
#endif
var res = dialog is OpenFileDialog
? UnmanagedMethods.GetOpenFileName(new IntPtr(pofn))
: UnmanagedMethods.GetSaveFileName(new IntPtr(pofn));
// Restore the old current directory, since GetOpenFileName and GetSaveFileName change it after they're called
#if !NETSTANDARD
Environment.CurrentDirectory = currentDirectory;
#endif
if (!res)
return null;
@ -155,15 +152,16 @@ namespace Avalonia.Win32
public Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent)
{
#if NETSTANDARD
throw new NotImplementedException();
#else
return Task.Factory.StartNew(() =>
{
string result = string.Empty;
var hWnd = parent?.Handle?.Handle ?? IntPtr.Zero;
var frm = (IFileDialog)(new UnmanagedMethods.FileOpenDialogRCW());
var clsid = Guid.Parse("DC1C5A9C-E88A-4DDE-A5A1-60F82A20AEF7");
var iid = Guid.Parse("42F85136-DB7E-439C-85F1-E4075D135FC8");
UnmanagedMethods.CoCreateInstance(ref clsid, IntPtr.Zero, 1, ref iid, out var unk);
var frm = (IFileDialog)unk;
uint options;
frm.GetOptions(out options);
options |= (uint)(UnmanagedMethods.FOS.FOS_PICKFOLDERS | UnmanagedMethods.FOS.FOS_FORCEFILESYSTEM | UnmanagedMethods.FOS.FOS_NOVALIDATE | UnmanagedMethods.FOS.FOS_NOTESTFILECREATE | UnmanagedMethods.FOS.FOS_DONTADDTORECENT);
@ -214,7 +212,6 @@ namespace Avalonia.Win32
return result;
});
#endif
}
}
}

22
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -750,5 +750,27 @@ namespace Avalonia.Win32
return (int)(ptr.ToInt64() & 0xffffffff);
}
public void ShowTaskbarIcon(bool value)
{
var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, -20);
style &= ~(UnmanagedMethods.WindowStyles.WS_VISIBLE);
style |= UnmanagedMethods.WindowStyles.WS_EX_TOOLWINDOW;
if (value)
style |= UnmanagedMethods.WindowStyles.WS_EX_APPWINDOW;
else
style &= ~(UnmanagedMethods.WindowStyles.WS_EX_APPWINDOW);
WINDOWPLACEMENT windowPlacement = UnmanagedMethods.WINDOWPLACEMENT.Default;
if (UnmanagedMethods.GetWindowPlacement(_hwnd, ref windowPlacement))
{
//Toggle to make the styles stick
UnmanagedMethods.ShowWindow(_hwnd, ShowWindowCommand.Hide);
UnmanagedMethods.SetWindowLong(_hwnd, -20, (uint)style);
UnmanagedMethods.ShowWindow(_hwnd, windowPlacement.ShowCmd);
}
}
}
}

2
tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
<PropertyGroup>
<TargetFrameworks>net461;netcoreapp1.1</TargetFrameworks>
<TargetFrameworks>net461;netcoreapp2.0</TargetFrameworks>
<OutputType>Library</OutputType>
</PropertyGroup>
<Import Project="..\..\build\UnitTests.NetCore.targets" />

2
tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
<PropertyGroup>
<TargetFrameworks>net461;netcoreapp1.1</TargetFrameworks>
<TargetFrameworks>net461;netcoreapp2.0</TargetFrameworks>
<OutputType>Library</OutputType>
</PropertyGroup>
<Import Project="..\..\build\UnitTests.NetCore.targets" />

217
tests/Avalonia.Controls.UnitTests/ControlTests_Resources.cs

@ -0,0 +1,217 @@
// 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 Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Avalonia.Styling;
using Avalonia.UnitTests;
using Xunit;
namespace Avalonia.Controls.UnitTests
{
public class ControlTests_Resources
{
[Fact]
public void FindResource_Should_Find_Control_Resource()
{
var target = new Control
{
Resources =
{
{ "foo", "foo-value" },
}
};
Assert.Equal("foo-value", target.FindResource("foo"));
}
[Fact]
public void FindResource_Should_Find_Control_Resource_In_Parent()
{
Control target;
var root = new Decorator
{
Resources =
{
{ "foo", "foo-value" },
},
Child = target = new Control(),
};
Assert.Equal("foo-value", target.FindResource("foo"));
}
[Fact]
public void FindResource_Should_Find_Application_Resource()
{
Control target;
var app = new Application
{
Resources =
{
{ "foo", "foo-value" },
},
};
var root = new TestRoot
{
Child = target = new Control(),
StylingParent = app,
};
Assert.Equal("foo-value", target.FindResource("foo"));
}
[Fact]
public void FindResource_Should_Find_Style_Resource()
{
var target = new Control
{
Styles =
{
new Style
{
Resources =
{
{ "foo", "foo-value" },
}
}
},
Resources =
{
{ "bar", "bar-value" },
},
};
Assert.Equal("foo-value", target.FindResource("foo"));
}
[Fact]
public void FindResource_Should_Find_Styles_Resource()
{
var target = new Control
{
Styles =
{
new Styles
{
Resources =
{
{ "foo", "foo-value" },
}
}
},
Resources =
{
{ "bar", "bar-value" },
},
};
Assert.Equal("foo-value", target.FindResource("foo"));
}
[Fact]
public void FindResource_Should_Find_Application_Style_Resource()
{
Control target;
var app = new Application
{
Styles =
{
new Style
{
Resources =
{
{ "foo", "foo-value" },
},
}
},
Resources =
{
{ "bar", "bar-value" },
},
};
var root = new TestRoot
{
Child = target = new Control(),
StylingParent = app,
};
Assert.Equal("foo-value", target.FindResource("foo"));
}
[Fact]
public void Adding_Resource_Should_Call_Raise_ResourceChanged_On_Logical_Children()
{
Border child;
var target = new ContentControl
{
Content = child = new Border(),
Template = ContentControlTemplate(),
};
var raisedOnTarget = false;
var raisedOnPresenter = false;
var raisedOnChild = false;
target.Measure(Size.Infinity);
target.ResourcesChanged += (_, __) => raisedOnTarget = true;
target.Presenter.ResourcesChanged += (_, __) => raisedOnPresenter = true;
child.ResourcesChanged += (_, __) => raisedOnChild = true;
target.Resources.Add("foo", "bar");
Assert.True(raisedOnTarget);
Assert.False(raisedOnPresenter);
Assert.True(raisedOnChild);
}
[Fact]
public void Adding_Resource_To_Styles_Should_Raise_ResourceChanged()
{
var target = new Decorator();
var raised = false;
target.ResourcesChanged += (_, __) => raised = true;
target.Styles.Resources.Add("foo", "bar");
Assert.True(raised);
}
[Fact]
public void Adding_Resource_To_Nested_Style_Should_Raise_ResourceChanged()
{
Style style;
var target = new Decorator
{
Styles =
{
(style = new Style()),
}
};
var raised = false;
target.ResourcesChanged += (_, __) => raised = true;
style.Resources.Add("foo", "bar");
Assert.True(raised);
}
private IControlTemplate ContentControlTemplate()
{
return new FuncControlTemplate<ContentControl>(x =>
new ContentPresenter
{
Name = "PART_ContentPresenter",
[!ContentPresenter.ContentProperty] = x[!ContentControl.ContentProperty],
});
}
}
}

17
tests/Avalonia.Controls.UnitTests/TopLevelTests.cs

@ -219,6 +219,23 @@ namespace Avalonia.Controls.UnitTests
}
}
[Fact]
public void Adding_Resource_To_Application_Should_Raise_ResourcesChanged()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var impl = new Mock<ITopLevelImpl>();
impl.SetupAllProperties();
var target = new TestTopLevel(impl.Object);
var raised = false;
target.ResourcesChanged += (_, __) => raised = true;
Application.Current.Resources.Add("foo", "bar");
Assert.True(raised);
}
}
private FuncControlTemplate<TestTopLevel> CreateTemplate()
{
return new FuncControlTemplate<TestTopLevel>(x =>

2
tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
<PropertyGroup>
<TargetFrameworks>net461;netcoreapp1.1</TargetFrameworks>
<TargetFrameworks>net461;netcoreapp2.0</TargetFrameworks>
<OutputType>Library</OutputType>
</PropertyGroup>
<Import Project="..\..\build\UnitTests.NetCore.targets" />

2
tests/Avalonia.Interactivity.UnitTests/Avalonia.Interactivity.UnitTests.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
<PropertyGroup>
<TargetFrameworks>net461;netcoreapp1.1</TargetFrameworks>
<TargetFrameworks>net461;netcoreapp2.0</TargetFrameworks>
<OutputType>Library</OutputType>
</PropertyGroup>
<Import Project="..\..\build\UnitTests.NetCore.targets" />

2
tests/Avalonia.Layout.UnitTests/Avalonia.Layout.UnitTests.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
<PropertyGroup>
<TargetFrameworks>net461;netcoreapp1.1</TargetFrameworks>
<TargetFrameworks>net461;netcoreapp2.0</TargetFrameworks>
<OutputType>Library</OutputType>
</PropertyGroup>
<Import Project="..\..\build\UnitTests.NetCore.targets" />

4
tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs

@ -162,6 +162,10 @@ namespace Avalonia.Layout.UnitTests
private void RegisterServices()
{
var globalStyles = new Mock<IGlobalStyles>();
var globalStylesResources = globalStyles.As<IResourceNode>();
var outObj = (object)10;
globalStylesResources.Setup(x => x.TryGetResource("FontSizeNormal", out outObj)).Returns(true);
var renderInterface = new Mock<IPlatformRenderInterface>();
renderInterface.Setup(x =>
x.CreateFormattedText(

2
tests/Avalonia.Markup.UnitTests/Avalonia.Markup.UnitTests.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
<PropertyGroup>
<TargetFrameworks>net461;netcoreapp1.1</TargetFrameworks>
<TargetFrameworks>net461;netcoreapp2.0</TargetFrameworks>
<OutputType>Library</OutputType>
</PropertyGroup>
<Import Project="..\..\build\UnitTests.NetCore.targets" />

2
tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
<PropertyGroup>
<TargetFrameworks>net461;netcoreapp1.1</TargetFrameworks>
<TargetFrameworks>net461;netcoreapp2.0</TargetFrameworks>
<OutputType>Library</OutputType>
</PropertyGroup>
<Import Project="..\..\build\UnitTests.NetCore.targets" />

55
tests/Avalonia.Markup.Xaml.UnitTests/Data/ResourceIncludeTests.cs

@ -0,0 +1,55 @@
using System;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.UnitTests;
using Xunit;
namespace Avalonia.Markup.Xaml.UnitTests.Data
{
public class ResourceIncludeTests
{
public class StaticResourceExtensionTests
{
[Fact]
public void ResourceInclude_Loads_ResourceDictionary()
{
var includeXaml = @"
<ResourceDictionary xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<SolidColorBrush x:Key='brush'>#ff506070</SolidColorBrush>
</ResourceDictionary>
";
using (StartWithResources(("test:include.xaml", includeXaml)))
{
var xaml = @"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceInclude Source='test:include.xaml'/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Border Name='border' Background='{StaticResource brush}'/>
</UserControl>";
var loader = new AvaloniaXamlLoader();
var userControl = (UserControl)loader.Load(xaml);
var border = userControl.FindControl<Border>("border");
var brush = (SolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
}
private IDisposable StartWithResources(params (string, string)[] assets)
{
var assetLoader = new MockAssetLoader(assets);
var services = new TestServices(assetLoader: assetLoader);
return UnitTestApplication.Start(services);
}
}
}
}

660
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs

@ -0,0 +1,660 @@
// 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.Linq;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Avalonia.Markup.Xaml.Data;
using Avalonia.Media;
using Avalonia.Styling;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
using Xunit;
namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
{
public class DynamicResourceExtensionTests
{
[Fact]
public void DynamicResource_Can_Be_Assigned_To_Property()
{
var xaml = @"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<UserControl.Resources>
<SolidColorBrush x:Key='brush'>#ff506070</SolidColorBrush>
</UserControl.Resources>
<Border Name='border' Background='{DynamicResource brush}'/>
</UserControl>";
var loader = new AvaloniaXamlLoader();
var userControl = (UserControl)loader.Load(xaml);
var border = userControl.FindControl<Border>("border");
DelayedBinding.ApplyBindings(border);
var brush = (SolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
[Fact]
public void DynamicResource_Can_Be_Assigned_To_Attached_Property()
{
var xaml = @"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<UserControl.Resources>
<x:Int32 x:Key='col'>5</x:Int32>
</UserControl.Resources>
<Border Name='border' Grid.Column='{DynamicResource col}'/>
</UserControl>";
var loader = new AvaloniaXamlLoader();
var userControl = (UserControl)loader.Load(xaml);
var border = userControl.FindControl<Border>("border");
DelayedBinding.ApplyBindings(border);
Assert.Equal(5, Grid.GetColumn(border));
}
[Fact]
public void DynamicResource_From_Style_Can_Be_Assigned_To_Property()
{
var xaml = @"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<UserControl.Styles>
<Style>
<Style.Resources>
<SolidColorBrush x:Key='brush'>#ff506070</SolidColorBrush>
</Style.Resources>
</Style>
</UserControl.Styles>
<Border Name='border' Background='{DynamicResource brush}'/>
</UserControl>";
var loader = new AvaloniaXamlLoader();
var userControl = (UserControl)loader.Load(xaml);
var border = userControl.FindControl<Border>("border");
DelayedBinding.ApplyBindings(border);
var brush = (SolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
[Fact]
public void DynamicResource_From_MergedDictionary_Can_Be_Assigned_To_Property()
{
var xaml = @"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary>
<SolidColorBrush x:Key='brush'>#ff506070</SolidColorBrush>
</ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Border Name='border' Background='{DynamicResource brush}'/>
</UserControl>";
var loader = new AvaloniaXamlLoader();
var userControl = (UserControl)loader.Load(xaml);
var border = userControl.FindControl<Border>("border");
DelayedBinding.ApplyBindings(border);
var brush = (SolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
[Fact]
public void DynamicResource_From_MergedDictionary_In_Style_Can_Be_Assigned_To_Property()
{
var xaml = @"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<UserControl.Styles>
<Style>
<Style.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary>
<SolidColorBrush x:Key='brush'>#ff506070</SolidColorBrush>
</ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Style.Resources>
</Style>
</UserControl.Styles>
<Border Name='border' Background='{DynamicResource brush}'/>
</UserControl>";
var loader = new AvaloniaXamlLoader();
var userControl = (UserControl)loader.Load(xaml);
var border = userControl.FindControl<Border>("border");
DelayedBinding.ApplyBindings(border);
var brush = (SolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
[Fact]
public void DynamicResource_From_Application_Can_Be_Assigned_To_Property_In_Window()
{
using (StyledWindow())
{
Application.Current.Resources.Add("brush", new SolidColorBrush(0xff506070));
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Border Name='border' Background='{DynamicResource brush}'/>
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var border = window.FindControl<Border>("border");
var brush = (SolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
}
[Fact]
public void DynamicResource_From_Application_Can_Be_Assigned_To_Property_In_UserControl()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
Application.Current.Resources.Add("brush", new SolidColorBrush(0xff506070));
var xaml = @"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Border Name='border' Background='{DynamicResource brush}'/>
</UserControl>";
var loader = new AvaloniaXamlLoader();
var userControl = (UserControl)loader.Load(xaml);
var border = userControl.FindControl<Border>("border");
// We don't actually know where the global styles are until we attach the control
// to a window, as Window has StylingParent set to Application.
var window = new Window { Content = userControl };
window.Show();
var brush = (SolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
}
[Fact]
public void DynamicResource_Can_Be_Assigned_To_Setter()
{
using (StyledWindow())
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Resources>
<SolidColorBrush x:Key='brush'>#ff506070</SolidColorBrush>
</Window.Resources>
<Window.Styles>
<Style Selector='Button'>
<Setter Property='Background' Value='{DynamicResource brush}'/>
</Style>
</Window.Styles>
<Button Name='button'/>
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var button = window.FindControl<Button>("button");
var brush = (SolidColorBrush)button.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
}
[Fact]
public void DynamicResource_From_Style_Can_Be_Assigned_To_Setter()
{
using (StyledWindow())
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Styles>
<Style>
<Style.Resources>
<SolidColorBrush x:Key='brush'>#ff506070</SolidColorBrush>
</Style.Resources>
</Style>
<Style Selector='Button'>
<Setter Property='Background' Value='{DynamicResource brush}'/>
</Style>
</Window.Styles>
<Button Name='button'/>
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var button = window.FindControl<Button>("button");
var brush = (SolidColorBrush)button.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
}
[Fact]
public void DynamicResource_Can_Be_Assigned_To_Setter_In_Styles_File()
{
var styleXaml = @"
<Styles xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Styles.Resources>
<SolidColorBrush x:Key='brush'>#ff506070</SolidColorBrush>
</Styles.Resources>
<Style Selector='Border'>
<Setter Property='Background' Value='{DynamicResource brush}'/>
</Style>
</Styles>";
using (StyledWindow(assets: ("test:style.xaml", styleXaml)))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Styles>
<StyleInclude Source='test:style.xaml'/>
</Window.Styles>
<Border Name='border'/>
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var border = window.FindControl<Border>("border");
var brush = (SolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
}
[Fact]
public void DynamicResource_Can_Be_Assigned_To_Property_In_ControlTemplate_In_Styles_File()
{
var styleXaml = @"
<Styles xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Styles.Resources>
<SolidColorBrush x:Key='brush'>#ff506070</SolidColorBrush>
</Styles.Resources>
<Style Selector='Button'>
<Setter Property='Template'>
<ControlTemplate>
<Border Name='border' Background='{DynamicResource brush}'/>
</ControlTemplate>
</Setter>
</Style>
</Styles>";
using (StyledWindow(assets: ("test:style.xaml", styleXaml)))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Styles>
<StyleInclude Source='test:style.xaml'/>
</Window.Styles>
<Button Name='button'/>
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var button = window.FindControl<Button>("button");
window.Show();
var border = (Border)button.GetVisualChildren().Single();
var brush = (SolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
}
[Fact]
public void DynamicResource_Can_Be_Assigned_To_Resource_Property()
{
var xaml = @"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<UserControl.Resources>
<Color x:Key='color'>#ff506070</Color>
<SolidColorBrush x:Key='brush' Color='{DynamicResource color}'/>
</UserControl.Resources>
<Border Name='border' Background='{DynamicResource brush}'/>
</UserControl>";
var loader = new AvaloniaXamlLoader();
var userControl = (UserControl)loader.Load(xaml);
var border = userControl.FindControl<Border>("border");
DelayedBinding.ApplyBindings(border);
var brush = (SolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
[Fact]
public void DynamicResource_Can_Be_Assigned_To_ItemTemplate_Property()
{
var xaml = @"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<UserControl.Resources>
<DataTemplate x:Key='PurpleData'>
<TextBlock Text='{Binding Name}' Background='Purple'/>
</DataTemplate>
</UserControl.Resources>
<ListBox Name='listBox' ItemTemplate='{DynamicResource PurpleData}'/>
</UserControl>";
var loader = new AvaloniaXamlLoader();
var userControl = (UserControl)loader.Load(xaml);
var listBox = userControl.FindControl<ListBox>("listBox");
DelayedBinding.ApplyBindings(listBox);
Assert.NotNull(listBox.ItemTemplate);
}
[Fact]
public void DynamicResource_Tracks_Added_Resource()
{
var xaml = @"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Border Name='border' Background='{DynamicResource brush}'/>
</UserControl>";
var loader = new AvaloniaXamlLoader();
var userControl = (UserControl)loader.Load(xaml);
var border = userControl.FindControl<Border>("border");
DelayedBinding.ApplyBindings(border);
Assert.Null(border.Background);
userControl.Resources.Add("brush", new SolidColorBrush(0xff506070));
var brush = (SolidColorBrush)border.Background;
Assert.NotNull(brush);
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
[Fact]
public void DynamicResource_Tracks_Added_Style_Resource()
{
var xaml = @"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Border Name='border' Background='{DynamicResource brush}'/>
</UserControl>";
var loader = new AvaloniaXamlLoader();
var userControl = (UserControl)loader.Load(xaml);
var border = userControl.FindControl<Border>("border");
DelayedBinding.ApplyBindings(border);
Assert.Null(border.Background);
userControl.Styles.Resources.Add("brush", new SolidColorBrush(0xff506070));
var brush = (SolidColorBrush)border.Background;
Assert.NotNull(brush);
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
[Fact]
public void DynamicResource_Tracks_Added_Nested_Style_Resource()
{
var xaml = @"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<UserControl.Styles>
<Style>
</Style>
</UserControl.Styles>
<Border Name='border' Background='{DynamicResource brush}'/>
</UserControl>";
var loader = new AvaloniaXamlLoader();
var userControl = (UserControl)loader.Load(xaml);
var border = userControl.FindControl<Border>("border");
DelayedBinding.ApplyBindings(border);
Assert.Null(border.Background);
((Style)userControl.Styles[0]).Resources.Add("brush", new SolidColorBrush(0xff506070));
var brush = (SolidColorBrush)border.Background;
Assert.NotNull(brush);
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
[Fact]
public void DynamicResource_Tracks_Added_MergedResource()
{
var xaml = @"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Border Name='border' Background='{DynamicResource brush}'/>
</UserControl>";
var loader = new AvaloniaXamlLoader();
var userControl = (UserControl)loader.Load(xaml);
var border = userControl.FindControl<Border>("border");
DelayedBinding.ApplyBindings(border);
Assert.Null(border.Background);
((IResourceDictionary)userControl.Resources.MergedDictionaries[0]).Add("brush", new SolidColorBrush(0xff506070));
var brush = (SolidColorBrush)border.Background;
Assert.NotNull(brush);
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
[Fact]
public void DynamicResource_Tracks_Added_MergedResource_Dictionary()
{
var xaml = @"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Border Name='border' Background='{DynamicResource brush}'/>
</UserControl>";
var loader = new AvaloniaXamlLoader();
var userControl = (UserControl)loader.Load(xaml);
var border = userControl.FindControl<Border>("border");
DelayedBinding.ApplyBindings(border);
Assert.Null(border.Background);
var dictionary = new ResourceDictionary
{
{ "brush", new SolidColorBrush(0xff506070) },
};
userControl.Resources.MergedDictionaries.Add(dictionary);
var brush = (SolidColorBrush)border.Background;
Assert.NotNull(brush);
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
[Fact]
public void DynamicResource_Tracks_Added_Style_MergedResource_Dictionary()
{
var xaml = @"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<UserControl.Styles>
<Style>
</Style>
</UserControl.Styles>
<Border Name='border' Background='{DynamicResource brush}'/>
</UserControl>";
var loader = new AvaloniaXamlLoader();
var userControl = (UserControl)loader.Load(xaml);
var border = userControl.FindControl<Border>("border");
DelayedBinding.ApplyBindings(border);
Assert.Null(border.Background);
var dictionary = new ResourceDictionary
{
{ "brush", new SolidColorBrush(0xff506070) },
};
((Style)userControl.Styles[0]).Resources.MergedDictionaries.Add(dictionary);
var brush = (SolidColorBrush)border.Background;
Assert.NotNull(brush);
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
[Fact]
public void DynamicResource_Can_Be_Found_Across_Xaml_Style_Files()
{
var style1Xaml = @"
<Style xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Style.Resources>
<Color x:Key='Red'>Red</Color>
</Style.Resources>
</Style>";
var style2Xaml = @"
<Style xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Style.Resources>
<SolidColorBrush x:Key='RedBrush' Color='{DynamicResource Red}'/>
</Style.Resources>
</Style>";
using (StyledWindow(
("test:style1.xaml", style1Xaml),
("test:style2.xaml", style2Xaml)))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Styles>
<StyleInclude Source='test:style1.xaml'/>
<StyleInclude Source='test:style2.xaml'/>
</Window.Styles>
<Border Name='border' Background='{DynamicResource RedBrush}'/>
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var border = window.FindControl<Border>("border");
var borderBrush = (ISolidColorBrush)border.Background;
Assert.NotNull(borderBrush);
Assert.Equal(0xffff0000, borderBrush.Color.ToUint32());
}
}
[Fact]
public void Control_Property_Is_Updated_When_Parent_Is_Changed()
{
var xaml = @"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<UserControl.Resources>
<SolidColorBrush x:Key='brush'>#ff506070</SolidColorBrush>
</UserControl.Resources>
<Border Name='border' Background='{DynamicResource brush}'/>
</UserControl>";
var loader = new AvaloniaXamlLoader();
var userControl = (UserControl)loader.Load(xaml);
var border = userControl.FindControl<Border>("border");
DelayedBinding.ApplyBindings(border);
var brush = (SolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
userControl.Content = null;
Assert.Null(border.Background);
userControl.Content = border;
brush = (SolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
private IDisposable StyledWindow(params (string, string)[] assets)
{
var services = TestServices.StyledWindow.With(
assetLoader: new MockAssetLoader(assets),
theme: () => new Styles
{
WindowStyle(),
});
return UnitTestApplication.Start(services);
}
private Style WindowStyle()
{
return new Style(x => x.OfType<Window>())
{
Setters =
{
new Setter(
Window.TemplateProperty,
new FuncControlTemplate<Window>(x =>
new ContentPresenter
{
Name = "PART_ContentPresenter",
[!ContentPresenter.ContentProperty] = x[!Window.ContentProperty],
}))
}
};
}
}
}

476
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs

@ -0,0 +1,476 @@
// 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.Linq;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Avalonia.Media;
using Avalonia.Styling;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
using Xunit;
namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
{
public class StaticResourceExtensionTests
{
[Fact]
public void StaticResource_Can_Be_Assigned_To_Property()
{
var xaml = @"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<UserControl.Resources>
<SolidColorBrush x:Key='brush'>#ff506070</SolidColorBrush>
</UserControl.Resources>
<Border Name='border' Background='{StaticResource brush}'/>
</UserControl>";
var loader = new AvaloniaXamlLoader();
var userControl = (UserControl)loader.Load(xaml);
var border = userControl.FindControl<Border>("border");
var brush = (SolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
[Fact]
public void StaticResource_Can_Be_Assigned_To_Attached_Property()
{
var xaml = @"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<UserControl.Resources>
<x:Int32 x:Key='col'>5</x:Int32>
</UserControl.Resources>
<Border Name='border' Grid.Column='{StaticResource col}'/>
</UserControl>";
var loader = new AvaloniaXamlLoader();
var userControl = (UserControl)loader.Load(xaml);
var border = userControl.FindControl<Border>("border");
Assert.Equal(5, Grid.GetColumn(border));
}
[Fact]
public void StaticResource_From_Style_Can_Be_Assigned_To_Property()
{
var xaml = @"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<UserControl.Styles>
<Style>
<Style.Resources>
<SolidColorBrush x:Key='brush'>#ff506070</SolidColorBrush>
</Style.Resources>
</Style>
</UserControl.Styles>
<Border Name='border' Background='{StaticResource brush}'/>
</UserControl>";
var loader = new AvaloniaXamlLoader();
var userControl = (UserControl)loader.Load(xaml);
var border = userControl.FindControl<Border>("border");
var brush = (SolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
[Fact]
public void StaticResource_From_Application_Can_Be_Assigned_To_Property_In_Window()
{
using (StyledWindow())
{
Application.Current.Resources.Add("brush", new SolidColorBrush(0xff506070));
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Border Name='border' Background='{StaticResource brush}'/>
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var border = window.FindControl<Border>("border");
var brush = (SolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
}
[Fact]
public void StaticResource_From_MergedDictionary_Can_Be_Assigned_To_Property()
{
var xaml = @"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary>
<SolidColorBrush x:Key='brush'>#ff506070</SolidColorBrush>
</ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Border Name='border' Background='{StaticResource brush}'/>
</UserControl>";
var loader = new AvaloniaXamlLoader();
var userControl = (UserControl)loader.Load(xaml);
var border = userControl.FindControl<Border>("border");
var brush = (SolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
[Fact]
public void StaticResource_From_MergedDictionary_In_Style_Can_Be_Assigned_To_Property()
{
var xaml = @"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<UserControl.Styles>
<Style>
<Style.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary>
<SolidColorBrush x:Key='brush'>#ff506070</SolidColorBrush>
</ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Style.Resources>
</Style>
</UserControl.Styles>
<Border Name='border' Background='{StaticResource brush}'/>
</UserControl>";
var loader = new AvaloniaXamlLoader();
var userControl = (UserControl)loader.Load(xaml);
var border = userControl.FindControl<Border>("border");
var brush = (SolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
[Fact]
public void StaticResource_From_Application_Can_Be_Assigned_To_Property_In_UserControl()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
Application.Current.Resources.Add("brush", new SolidColorBrush(0xff506070));
var xaml = @"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Border Name='border' Background='{StaticResource brush}'/>
</UserControl>";
var loader = new AvaloniaXamlLoader();
var userControl = (UserControl)loader.Load(xaml);
var border = userControl.FindControl<Border>("border");
// We don't actually know where the global styles are until we attach the control
// to a window, as Window has StylingParent set to Application.
var window = new Window { Content = userControl };
window.Show();
var brush = (SolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
}
[Fact]
public void StaticResource_Can_Be_Assigned_To_Setter()
{
using (StyledWindow())
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Resources>
<SolidColorBrush x:Key='brush'>#ff506070</SolidColorBrush>
</Window.Resources>
<Window.Styles>
<Style Selector='Button'>
<Setter Property='Background' Value='{StaticResource brush}'/>
</Style>
</Window.Styles>
<Button Name='button'/>
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var button = window.FindControl<Button>("button");
var brush = (SolidColorBrush)button.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
}
[Fact]
public void StaticResource_From_Style_Can_Be_Assigned_To_Setter()
{
using (StyledWindow())
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Styles>
<Style>
<Style.Resources>
<SolidColorBrush x:Key='brush'>#ff506070</SolidColorBrush>
</Style.Resources>
</Style>
<Style Selector='Button'>
<Setter Property='Background' Value='{StaticResource brush}'/>
</Style>
</Window.Styles>
<Button Name='button'/>
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var button = window.FindControl<Button>("button");
var brush = (SolidColorBrush)button.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
}
[Fact]
public void StaticResource_Can_Be_Assigned_To_Setter_In_Styles_File()
{
var styleXaml = @"
<Styles xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Styles.Resources>
<SolidColorBrush x:Key='brush'>#ff506070</SolidColorBrush>
</Styles.Resources>
<Style Selector='Border'>
<Setter Property='Background' Value='{StaticResource brush}'/>
</Style>
</Styles>";
using (StyledWindow(assets: ("test:style.xaml", styleXaml)))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Styles>
<StyleInclude Source='test:style.xaml'/>
</Window.Styles>
<Border Name='border'/>
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var border = window.FindControl<Border>("border");
var brush = (SolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
}
[Fact]
public void StaticResource_Can_Be_Assigned_To_Resource_Property()
{
var xaml = @"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<UserControl.Resources>
<Color x:Key='color'>#ff506070</Color>
<SolidColorBrush x:Key='brush' Color='{StaticResource color}'/>
</UserControl.Resources>
<Border Name='border' Background='{StaticResource brush}'/>
</UserControl>";
var loader = new AvaloniaXamlLoader();
var userControl = (UserControl)loader.Load(xaml);
var border = userControl.FindControl<Border>("border");
var brush = (SolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
[Fact]
public void StaticResource_Can_Be_Assigned_To_Resource_Property_In_Styles_File()
{
var xaml = @"
<Styles xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Styles.Resources>
<Color x:Key='color'>#ff506070</Color>
<SolidColorBrush x:Key='brush' Color='{StaticResource color}'/>
</Styles.Resources>
</Styles>";
var loader = new AvaloniaXamlLoader();
var styles = (Styles)loader.Load(xaml);
var brush = (SolidColorBrush)styles.Resources["brush"];
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
[Fact(Skip = "Not yet supported by Portable.Xaml")]
public void StaticResource_Can_Be_Assigned_To_Property_In_ControlTemplate_In_Styles_File()
{
var styleXaml = @"
<Styles xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Styles.Resources>
<SolidColorBrush x:Key='brush'>#ff506070</SolidColorBrush>
</Styles.Resources>
<Style Selector='Button'>
<Setter Property='Template'>
<ControlTemplate>
<Border Name='border' Background='{StaticResource brush}'/>
</ControlTemplate>
</Setter>
</Style>
</Styles>";
using (StyledWindow(assets: ("test:style.xaml", styleXaml)))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Styles>
<StyleInclude Source='test:style.xaml'/>
</Window.Styles>
<Button Name='button'/>
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var button = window.FindControl<Button>("button");
window.Show();
var border = (Border)button.GetVisualChildren().Single();
var brush = (SolidColorBrush)border.Background;
// To make this work we somehow need to be able to get hold of the parent ambient
// context from Portable.Xaml. See TODO in StaticResourceExtension.
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
}
[Fact]
public void StaticResource_Can_Be_Assigned_To_ItemTemplate_Property()
{
var xaml = @"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<UserControl.Resources>
<DataTemplate x:Key='PurpleData'>
<TextBlock Text='{Binding Name}' Background='Purple'/>
</DataTemplate>
</UserControl.Resources>
<ListBox Name='listBox' ItemTemplate='{StaticResource PurpleData}'/>
</UserControl>";
var loader = new AvaloniaXamlLoader();
var userControl = (UserControl)loader.Load(xaml);
var listBox = userControl.FindControl<ListBox>("listBox");
Assert.NotNull(listBox.ItemTemplate);
}
[Fact]
public void StaticResource_Can_Be_Assigned_To_Converter()
{
using (StyledWindow())
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'>
<Window.Resources>
<local:TestValueConverter x:Key='converter' Append='bar'/>
</Window.Resources>
<TextBlock Name='textBlock' Text='{Binding Converter={StaticResource converter}}'/>
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var textBlock = window.FindControl<TextBlock>("textBlock");
window.DataContext = "foo";
window.ApplyTemplate();
Assert.Equal("foobar", textBlock.Text);
}
}
[Fact]
public void Control_Property_Is_Not_Updated_When_Parent_Is_Changed()
{
var xaml = @"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<UserControl.Resources>
<SolidColorBrush x:Key='brush'>#ff506070</SolidColorBrush>
</UserControl.Resources>
<Border Name='border' Background='{StaticResource brush}'/>
</UserControl>";
var loader = new AvaloniaXamlLoader();
var userControl = (UserControl)loader.Load(xaml);
var border = userControl.FindControl<Border>("border");
var brush = (SolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
userControl.Content = null;
brush = (SolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
private IDisposable StyledWindow(params (string, string)[] assets)
{
var services = TestServices.StyledWindow.With(
assetLoader: new MockAssetLoader(assets),
theme: () => new Styles
{
WindowStyle(),
});
return UnitTestApplication.Start(services);
}
private Style WindowStyle()
{
return new Style(x => x.OfType<Window>())
{
Setters =
{
new Setter(
Window.TemplateProperty,
new FuncControlTemplate<Window>(x =>
new ContentPresenter
{
Name = "PART_ContentPresenter",
[!ContentPresenter.ContentProperty] = x[!Window.ContentProperty],
}))
}
};
}
}
}

20
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/TestValueConverter.cs

@ -0,0 +1,20 @@
using System;
using System.Globalization;
namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
{
public class TestValueConverter : IValueConverter
{
public string Append { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value.ToString() + Append;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

6
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs

@ -480,13 +480,13 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
Assert.True(style.Resources.Count > 0);
var brush = style.FindResource("Brush") as SolidColorBrush;
style.TryGetResource("Brush", out var brush);
Assert.NotNull(brush);
Assert.Equal(Colors.White, brush.Color);
Assert.Equal(Colors.White, ((SolidColorBrush)brush).Color);
var d = (double)style.FindResource("Double");
style.TryGetResource("Double", out var d);
Assert.Equal(10.0, d);
}

12
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs

@ -141,7 +141,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var brush = (ISolidColorBrush)window.FindStyleResource("brush");
var brush = (ISolidColorBrush)window.FindResource("brush");
var button = window.FindControl<Button>("button");
DelayedBinding.ApplyBindings(button);
@ -169,9 +169,10 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
var loader = new AvaloniaXamlLoader();
var styles = (Styles)loader.Load(xaml);
var brush = (ISolidColorBrush)styles.FindResource("brush");
Assert.Equal(0xff506070, brush.Color.ToUint32());
styles.TryGetResource("brush", out var brush);
Assert.Equal(0xff506070, ((SolidColorBrush)brush).Color.ToUint32());
}
[Fact]
@ -194,9 +195,10 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
var loader = new AvaloniaXamlLoader();
var styles = (Styles)loader.Load(xaml);
var brush = (ISolidColorBrush)styles.FindResource("brush");
Assert.Equal(0xff506070, brush.Color.ToUint32());
styles.TryGetResource("brush", out var brush);
Assert.Equal(0xff506070, ((SolidColorBrush)brush).Color.ToUint32());
}
[Fact(Skip = "TODO: Issue #492")]

2
tests/Avalonia.RenderTests/Avalonia.Skia.RenderTests.csproj

@ -6,7 +6,7 @@
<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
<PropertyGroup>
<TargetFrameworks>netcoreapp1.1</TargetFrameworks>
<TargetFrameworks>netcoreapp2.0</TargetFrameworks>
<OutputPath>bin\Skia\$(Configuration)</OutputPath>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<EnableDefaultCompileItems>False</EnableDefaultCompileItems>

2
tests/Avalonia.Styling.UnitTests/Avalonia.Styling.UnitTests.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
<PropertyGroup>
<TargetFrameworks>net461;netcoreapp1.1</TargetFrameworks>
<TargetFrameworks>net461;netcoreapp2.0</TargetFrameworks>
<OutputType>Library</OutputType>
<NoWarn>CS0067</NoWarn>
</PropertyGroup>

175
tests/Avalonia.Styling.UnitTests/ResourceDictionaryTests.cs

@ -0,0 +1,175 @@
// 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 Avalonia.Controls;
using Xunit;
namespace Avalonia.Styling.UnitTests
{
public class ResourceDictionaryTests
{
[Fact]
public void TryGetResource_Should_Find_Resource()
{
var target = new ResourceDictionary
{
{ "foo", "bar" },
};
Assert.True(target.TryGetResource("foo", out var result));
Assert.Equal("bar", result);
}
[Fact]
public void TryGetResource_Should_Find_Resource_From_Merged_Dictionary()
{
var target = new ResourceDictionary
{
MergedDictionaries =
{
new ResourceDictionary
{
{ "foo", "bar" },
}
}
};
Assert.True(target.TryGetResource("foo", out var result));
Assert.Equal("bar", result);
}
[Fact]
public void TryGetResource_Should_Find_Resource_From_Itself_Before_Merged_Dictionary()
{
var target = new ResourceDictionary
{
{ "foo", "bar" },
};
target.MergedDictionaries.Add(new ResourceDictionary
{
{ "foo", "baz" },
});
Assert.True(target.TryGetResource("foo", out var result));
Assert.Equal("bar", result);
}
[Fact]
public void TryGetResource_Should_Find_Resource_From_Later_Merged_Dictionary()
{
var target = new ResourceDictionary
{
MergedDictionaries =
{
new ResourceDictionary
{
{ "foo", "bar" },
},
new ResourceDictionary
{
{ "foo", "baz" },
}
}
};
Assert.True(target.TryGetResource("foo", out var result));
Assert.Equal("baz", result);
}
[Fact]
public void ResourcesChanged_Should_Be_Raised_On_Resource_Add()
{
var target = new ResourceDictionary();
var raised = false;
target.ResourcesChanged += (_, __) => raised = true;
target.Add("foo", "bar");
Assert.True(raised);
}
[Fact]
public void ResourcesChanged_Should_Be_Raised_On_MergedDictionary_Add()
{
var target = new ResourceDictionary();
var raised = false;
target.ResourcesChanged += (_, __) => raised = true;
target.MergedDictionaries.Add(new ResourceDictionary
{
{ "foo", "bar" },
});
Assert.True(raised);
}
[Fact]
public void ResourcesChanged_Should_Not_Be_Raised_On_Empty_MergedDictionary_Add()
{
var target = new ResourceDictionary();
var raised = false;
target.ResourcesChanged += (_, __) => raised = true;
target.MergedDictionaries.Add(new ResourceDictionary());
Assert.False(raised);
}
[Fact]
public void ResourcesChanged_Should_Be_Raised_On_MergedDictionary_Remove()
{
var target = new ResourceDictionary
{
MergedDictionaries =
{
new ResourceDictionary { { "foo", "bar" } },
}
};
var raised = false;
target.ResourcesChanged += (_, __) => raised = true;
target.MergedDictionaries.RemoveAt(0);
Assert.True(raised);
}
[Fact]
public void ResourcesChanged_Should_Not_Be_Raised_On_Empty_MergedDictionary_Remove()
{
var target = new ResourceDictionary
{
MergedDictionaries =
{
new ResourceDictionary(),
}
};
var raised = false;
target.ResourcesChanged += (_, __) => raised = true;
target.MergedDictionaries.RemoveAt(0);
Assert.False(raised);
}
[Fact]
public void ResourcesChanged_Should_Be_Raised_On_MergedDictionary_Resource_Add()
{
var target = new ResourceDictionary
{
MergedDictionaries =
{
new ResourceDictionary(),
}
};
var raised = false;
target.ResourcesChanged += (_, __) => raised = true;
((IResourceDictionary)target.MergedDictionaries[0]).Add("foo", "bar");
Assert.True(raised);
}
}
}

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

Loading…
Cancel
Save