diff --git a/.gitmodules b/.gitmodules
index f446fe421e..98b6d076c1 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,7 +1,3 @@
-[submodule "src/Avalonia.ReactiveUI/src"]
- path = src/Avalonia.ReactiveUI/src
- url = https://github.com/AvaloniaUI/ReactiveUI.git
- branch = avalonia-snapshot
[submodule "src/Avalonia.HtmlRenderer/external"]
path = src/Avalonia.HtmlRenderer/external
url = https://github.com/AvaloniaUI/HTML-Renderer.git
diff --git a/.ncrunch/Avalonia.Direct2D1.RenderTests.v3.ncrunchproject b/.ncrunch/Avalonia.Direct2D1.RenderTests.v3.ncrunchproject
index 235da29767..a8c3abe8f2 100644
--- a/.ncrunch/Avalonia.Direct2D1.RenderTests.v3.ncrunchproject
+++ b/.ncrunch/Avalonia.Direct2D1.RenderTests.v3.ncrunchproject
@@ -1,6 +1,7 @@
1000
+ True
True
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Markup.UnitTests.netcoreapp1.1.v3.ncrunchproject b/.ncrunch/Avalonia.Markup.UnitTests.netcoreapp1.1.v3.ncrunchproject
new file mode 100644
index 0000000000..15d9efad87
--- /dev/null
+++ b/.ncrunch/Avalonia.Markup.UnitTests.netcoreapp1.1.v3.ncrunchproject
@@ -0,0 +1,9 @@
+
+
+
+
+ Avalonia.Markup.UnitTests.Data.Plugins.DataAnnotationsValidationPluginTests.Produces_Aggregate_BindingNotificationsx
+
+
+
+
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Skia.RenderTests.v3.ncrunchproject b/.ncrunch/Avalonia.Skia.RenderTests.v3.ncrunchproject
index 235da29767..a8c3abe8f2 100644
--- a/.ncrunch/Avalonia.Skia.RenderTests.v3.ncrunchproject
+++ b/.ncrunch/Avalonia.Skia.RenderTests.v3.ncrunchproject
@@ -1,6 +1,7 @@
1000
+ True
True
\ No newline at end of file
diff --git a/Avalonia.sln b/Avalonia.sln
index 391d02382e..56f33c2a6b 100644
--- a/Avalonia.sln
+++ b/Avalonia.sln
@@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
-VisualStudioVersion = 15.0.26228.4
+VisualStudioVersion = 15.0.26730.3
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Base", "src\Avalonia.Base\Avalonia.Base.csproj", "{B09B78D8-9B26-48B0-9149-D64A2F120F3F}"
EndProject
@@ -129,7 +129,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowsInteropTest", "sampl
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GtkInteropDemo", "samples\interop\GtkInteropDemo\GtkInteropDemo.csproj", "{BD7F352C-6DC1-4740-BAF2-2D34A038728C}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.DotNetFrameworkRuntime", "src\Avalonia.DotNetFrameworkRuntime\Avalonia.DotNetFrameworkRuntime.csproj", "{4A1ABB09-9047-4BD5-A4AD-A055E52C5EE0}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.DotNetFrameworkRuntime", "src\Avalonia.DotNetFrameworkRuntime\Avalonia.DotNetFrameworkRuntime.csproj", "{4A1ABB09-9047-4BD5-A4AD-A055E52C5EE0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RenderTest", "samples\RenderTest\RenderTest.csproj", "{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}"
EndProject
@@ -159,6 +159,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1
build\Microsoft.Reactive.Testing.props = build\Microsoft.Reactive.Testing.props
build\Moq.props = build\Moq.props
build\NetCore.props = build\NetCore.props
+ build\ReactiveUI.props = build\ReactiveUI.props
build\Rx.props = build\Rx.props
build\Serilog.props = build\Serilog.props
build\Serilog.Sinks.Trace.props = build\Serilog.Sinks.Trace.props
@@ -185,6 +186,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Win32.Interop", "s
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Skia.RenderTests", "tests\Avalonia.RenderTests\Avalonia.Skia.RenderTests.csproj", "{E1582370-37B3-403C-917F-8209551B1634}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OSX", "OSX", "{A59C4C0A-64DF-4621-B450-2BA00D6F61E2}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.MonoMac", "src\OSX\Avalonia.MonoMac\Avalonia.MonoMac.csproj", "{CBFD5788-567D-401B-9DFA-74E4224025A0}"
+EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13
@@ -2514,6 +2519,46 @@ Global
{E1582370-37B3-403C-917F-8209551B1634}.Release|Mono.Build.0 = Release|Any CPU
{E1582370-37B3-403C-917F-8209551B1634}.Release|x86.ActiveCfg = Release|Any CPU
{E1582370-37B3-403C-917F-8209551B1634}.Release|x86.Build.0 = Release|Any CPU
+ {CBFD5788-567D-401B-9DFA-74E4224025A0}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
+ {CBFD5788-567D-401B-9DFA-74E4224025A0}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
+ {CBFD5788-567D-401B-9DFA-74E4224025A0}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
+ {CBFD5788-567D-401B-9DFA-74E4224025A0}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
+ {CBFD5788-567D-401B-9DFA-74E4224025A0}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {CBFD5788-567D-401B-9DFA-74E4224025A0}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {CBFD5788-567D-401B-9DFA-74E4224025A0}.Ad-Hoc|Mono.ActiveCfg = Debug|Any CPU
+ {CBFD5788-567D-401B-9DFA-74E4224025A0}.Ad-Hoc|Mono.Build.0 = Debug|Any CPU
+ {CBFD5788-567D-401B-9DFA-74E4224025A0}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
+ {CBFD5788-567D-401B-9DFA-74E4224025A0}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
+ {CBFD5788-567D-401B-9DFA-74E4224025A0}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
+ {CBFD5788-567D-401B-9DFA-74E4224025A0}.AppStore|Any CPU.Build.0 = Debug|Any CPU
+ {CBFD5788-567D-401B-9DFA-74E4224025A0}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
+ {CBFD5788-567D-401B-9DFA-74E4224025A0}.AppStore|iPhone.Build.0 = Debug|Any CPU
+ {CBFD5788-567D-401B-9DFA-74E4224025A0}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {CBFD5788-567D-401B-9DFA-74E4224025A0}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {CBFD5788-567D-401B-9DFA-74E4224025A0}.AppStore|Mono.ActiveCfg = Debug|Any CPU
+ {CBFD5788-567D-401B-9DFA-74E4224025A0}.AppStore|Mono.Build.0 = Debug|Any CPU
+ {CBFD5788-567D-401B-9DFA-74E4224025A0}.AppStore|x86.ActiveCfg = Debug|Any CPU
+ {CBFD5788-567D-401B-9DFA-74E4224025A0}.AppStore|x86.Build.0 = Debug|Any CPU
+ {CBFD5788-567D-401B-9DFA-74E4224025A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CBFD5788-567D-401B-9DFA-74E4224025A0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CBFD5788-567D-401B-9DFA-74E4224025A0}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+ {CBFD5788-567D-401B-9DFA-74E4224025A0}.Debug|iPhone.Build.0 = Debug|Any CPU
+ {CBFD5788-567D-401B-9DFA-74E4224025A0}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {CBFD5788-567D-401B-9DFA-74E4224025A0}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {CBFD5788-567D-401B-9DFA-74E4224025A0}.Debug|Mono.ActiveCfg = Debug|Any CPU
+ {CBFD5788-567D-401B-9DFA-74E4224025A0}.Debug|Mono.Build.0 = Debug|Any CPU
+ {CBFD5788-567D-401B-9DFA-74E4224025A0}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {CBFD5788-567D-401B-9DFA-74E4224025A0}.Debug|x86.Build.0 = Debug|Any CPU
+ {CBFD5788-567D-401B-9DFA-74E4224025A0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CBFD5788-567D-401B-9DFA-74E4224025A0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CBFD5788-567D-401B-9DFA-74E4224025A0}.Release|iPhone.ActiveCfg = Release|Any CPU
+ {CBFD5788-567D-401B-9DFA-74E4224025A0}.Release|iPhone.Build.0 = Release|Any CPU
+ {CBFD5788-567D-401B-9DFA-74E4224025A0}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+ {CBFD5788-567D-401B-9DFA-74E4224025A0}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
+ {CBFD5788-567D-401B-9DFA-74E4224025A0}.Release|Mono.ActiveCfg = Release|Any CPU
+ {CBFD5788-567D-401B-9DFA-74E4224025A0}.Release|Mono.Build.0 = Release|Any CPU
+ {CBFD5788-567D-401B-9DFA-74E4224025A0}.Release|x86.ActiveCfg = Release|Any CPU
+ {CBFD5788-567D-401B-9DFA-74E4224025A0}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -2571,5 +2616,9 @@ Global
{638580B0-7910-40EF-B674-DCB34DA308CD} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9}
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E} = {B39A8919-9F95-48FE-AD7B-76E08B509888}
{E1582370-37B3-403C-917F-8209551B1634} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
+ {CBFD5788-567D-401B-9DFA-74E4224025A0} = {A59C4C0A-64DF-4621-B450-2BA00D6F61E2}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}
EndGlobalSection
EndGlobal
diff --git a/build/MonoMac.props b/build/MonoMac.props
new file mode 100644
index 0000000000..452177ca5d
--- /dev/null
+++ b/build/MonoMac.props
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/build/ReactiveUI.props b/build/ReactiveUI.props
new file mode 100644
index 0000000000..1e2ad69046
--- /dev/null
+++ b/build/ReactiveUI.props
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/build/Rx.props b/build/Rx.props
index 5d74a6b214..323026f5e2 100644
--- a/build/Rx.props
+++ b/build/Rx.props
@@ -1,11 +1,10 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/docs/tutorial/from-wpf.md b/docs/tutorial/from-wpf.md
index 0eb6e80327..d244fb076b 100644
--- a/docs/tutorial/from-wpf.md
+++ b/docs/tutorial/from-wpf.md
@@ -73,17 +73,6 @@ and includes `DirectProperty` for turning standard CLR properties into Avalonia
properties. The common base class of `StyledProperty` and `DirectProperty`
is `AvaloniaProperty`.
-# Resources
-
-There is no `Resources` collection on controls in Avalonia, however `Style`s
-do have a `Resources` collection for style-related resources. These can be
-referred to using the `{StyleResource}` markup extension both inside and outside
-styles.
-
-For non-style-related resources, we suggest defining them in code and referring
-to them in markup using the `{Static}` markup extension. To read more about the reasoning for this,
-see [this issue comment](https://github.com/AvaloniaUI/Avalonia/issues/462#issuecomment-191849723).
-
## Grid
Column and row definitions can be specified in Avalonia using strings, avoiding
diff --git a/packages.cake b/packages.cake
index 2c0298b669..7ada627200 100644
--- a/packages.cake
+++ b/packages.cake
@@ -110,6 +110,7 @@ public class Packages
var SplatVersion = packageVersions["Splat"].FirstOrDefault().Item1;
var SpracheVersion = packageVersions["Sprache"].FirstOrDefault().Item1;
var SystemReactiveVersion = packageVersions["System.Reactive"].FirstOrDefault().Item1;
+ var ReactiveUIVersion = packageVersions["reactiveui"].FirstOrDefault().Item1;
var SystemValueTupleVersion = packageVersions["System.ValueTuple"].FirstOrDefault().Item1;
SkiaSharpVersion = packageVersions["SkiaSharp"].FirstOrDefault().Item1;
SkiaSharpLinuxVersion = packageVersions["Avalonia.Skia.Linux.Natives"].FirstOrDefault().Item1;
@@ -123,6 +124,7 @@ public class Packages
context.Information("Package: Splat, version: {0}", SplatVersion);
context.Information("Package: Sprache, version: {0}", SpracheVersion);
context.Information("Package: System.Reactive, version: {0}", SystemReactiveVersion);
+ context.Information("Package: reactiveui, version: {0}", ReactiveUIVersion);
context.Information("Package: System.ValueTuple, version: {0}", SystemValueTupleVersion);
context.Information("Package: SkiaSharp, version: {0}", SkiaSharpVersion);
context.Information("Package: Avalonia.Skia.Linux.Natives, version: {0}", SkiaSharpLinuxVersion);
@@ -175,7 +177,6 @@ public class Packages
new [] { "./src/", "Avalonia.Visuals", ".xml" },
new [] { "./src/", "Avalonia.Styling", ".dll" },
new [] { "./src/", "Avalonia.Styling", ".xml" },
- new [] { "./src/", "Avalonia.ReactiveUI", ".dll" },
new [] { "./src/", "Avalonia.Themes.Default", ".dll" },
new [] { "./src/", "Avalonia.Themes.Default", ".xml" },
new [] { "./src/Markup/", "Avalonia.Markup", ".dll" },
@@ -272,7 +273,24 @@ public class Packages
},
BasePath = context.Directory("./src/Avalonia.HtmlRenderer/bin/" + parameters.DirSuffix + "/netstandard2.0"),
OutputDirectory = parameters.NugetRoot
- }
+ },
+ ///////////////////////////////////////////////////////////////////////////////
+ // Avalonia.ReactiveUI
+ ///////////////////////////////////////////////////////////////////////////////
+ new NuGetPackSettings()
+ {
+ Id = "Avalonia.ReactiveUI",
+ Dependencies = new DependencyBuilder(this)
+ {
+ new NuSpecDependency() { Id = "Avalonia", Version = parameters.Version },
+ }.Deps(new string[] {null}, "reactiveui"),
+ Files = new []
+ {
+ new NuSpecContent { Source = "Avalonia.ReactiveUI.dll", Target = "lib/netstandard2.0" }
+ },
+ BasePath = context.Directory("./src/Avalonia.ReactiveUI/bin/" + parameters.DirSuffix + "/netstandard2.0"),
+ OutputDirectory = parameters.NugetRoot
+ },
};
var nuspecNuGetSettingsMobile = new []
@@ -431,6 +449,20 @@ public class Packages
BasePath = context.Directory("./src/Skia/Avalonia.Skia/bin/" + parameters.DirSuffix + "/netstandard2.0"),
OutputDirectory = parameters.NugetRoot
},
+ new NuGetPackSettings()
+ {
+ Id = "Avalonia.MonoMac",
+ Dependencies = new DependencyBuilder(this)
+ {
+ new NuSpecDependency() { Id = "Avalonia", Version = parameters.Version }
+ }.Dep("MonoMac.NetStandard").ToArray(),
+ Files = new []
+ {
+ new NuSpecContent { Source = "netstandard2.0/Avalonia.MonoMac.dll", Target = "lib/netstandard2.0" },
+ },
+ BasePath = context.Directory("./src/OSX/Avalonia.MonoMac/bin/" + parameters.DirSuffix),
+ OutputDirectory = parameters.NugetRoot
+ },
///////////////////////////////////////////////////////////////////////////////
// Avalonia.Desktop
///////////////////////////////////////////////////////////////////////////////
@@ -446,10 +478,12 @@ public class Packages
new NuSpecDependency() { Id = "Avalonia.Win32", TargetFramework="net45", Version = parameters.Version },
new NuSpecDependency() { Id = "Avalonia.Skia", TargetFramework="net45", Version = parameters.Version },
new NuSpecDependency() { Id = "Avalonia.Gtk3", TargetFramework="net45", Version = parameters.Version },
+ new NuSpecDependency() { Id = "Avalonia.MonoMac", TargetFramework="net45", Version = parameters.Version },
//.NET Core
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 }
+ new NuSpecDependency() { Id = "Avalonia.Gtk3", TargetFramework="netcoreapp2.0", Version = parameters.Version },
+ new NuSpecDependency() { Id = "Avalonia.MonoMac", TargetFramework="netcoreapp2.0", Version = parameters.Version }
},
Files = new NuSpecContent[]
{
diff --git a/samples/BindingTest/App.xaml.cs b/samples/BindingTest/App.xaml.cs
index 7bcaf837a8..42e5716541 100644
--- a/samples/BindingTest/App.xaml.cs
+++ b/samples/BindingTest/App.xaml.cs
@@ -20,6 +20,7 @@ namespace BindingTest
AppBuilder.Configure()
.UsePlatformDetect()
+ .UseReactiveUI()
.Start();
}
diff --git a/samples/BindingTest/BindingTest.csproj b/samples/BindingTest/BindingTest.csproj
index eab656eb33..b4dd17194b 100644
--- a/samples/BindingTest/BindingTest.csproj
+++ b/samples/BindingTest/BindingTest.csproj
@@ -45,7 +45,6 @@
-
@@ -163,4 +162,5 @@
+
\ No newline at end of file
diff --git a/samples/BindingTest/ViewModels/MainWindowViewModel.cs b/samples/BindingTest/ViewModels/MainWindowViewModel.cs
index d8ea280abf..1116810ccb 100644
--- a/samples/BindingTest/ViewModels/MainWindowViewModel.cs
+++ b/samples/BindingTest/ViewModels/MainWindowViewModel.cs
@@ -28,15 +28,13 @@ namespace BindingTest.ViewModels
SelectedItems = new ObservableCollection();
- ShuffleItems = ReactiveCommand.Create();
- ShuffleItems.Subscribe(_ =>
+ ShuffleItems = ReactiveCommand.Create(() =>
{
var r = new Random();
Items.Move(r.Next(Items.Count), 1);
});
- StringValueCommand = ReactiveCommand.Create();
- StringValueCommand.Subscribe(param =>
+ StringValueCommand = ReactiveCommand.Create
diff --git a/src/Avalonia.ReactiveUI/AppBuilderExtensions.cs b/src/Avalonia.ReactiveUI/AppBuilderExtensions.cs
new file mode 100644
index 0000000000..e081763e99
--- /dev/null
+++ b/src/Avalonia.ReactiveUI/AppBuilderExtensions.cs
@@ -0,0 +1,24 @@
+// 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;
+using Avalonia.Threading;
+using ReactiveUI;
+using System;
+using System.Reactive.Concurrency;
+using System.Threading;
+
+namespace Avalonia
+{
+ public static class AppBuilderExtensions
+ {
+ public static TAppBuilder UseReactiveUI(this TAppBuilder builder)
+ where TAppBuilder : AppBuilderBase, new()
+ {
+ return builder.AfterSetup(_ =>
+ {
+ RxApp.MainThreadScheduler = AvaloniaScheduler.Instance;
+ });
+ }
+ }
+}
diff --git a/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj b/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj
index 835e2295e3..c29dbcd381 100644
--- a/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj
+++ b/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj
@@ -1,83 +1,10 @@
netstandard2.0
- False
false
-
- true
- full
- false
- bin\Debug\
- TRACE;DEBUG;MONO PORTABLE;NETSTANDARD1_1
- prompt
- 4
-
-
- pdbonly
- true
- bin\Release\
- TRACE;MONO PORTABLE;RELEASE;NETSTANDARD1_1
- prompt
- 4
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -88,5 +15,6 @@
+
\ No newline at end of file
diff --git a/src/Avalonia.ReactiveUI/Properties/AssemblyInfo.cs b/src/Avalonia.ReactiveUI/Properties/AssemblyInfo.cs
index c8a5c5cc41..c9ead6f6e6 100644
--- a/src/Avalonia.ReactiveUI/Properties/AssemblyInfo.cs
+++ b/src/Avalonia.ReactiveUI/Properties/AssemblyInfo.cs
@@ -3,7 +3,4 @@
using System.Reflection;
-// 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.ReactiveUI")]
diff --git a/src/Avalonia.ReactiveUI/Registrations.cs b/src/Avalonia.ReactiveUI/Registrations.cs
deleted file mode 100644
index 066aa46dc1..0000000000
--- a/src/Avalonia.ReactiveUI/Registrations.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright (c) The Avalonia Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-using System;
-using System.Reactive.Concurrency;
-using System.Threading;
-
-
-namespace ReactiveUI
-{
- ///
- /// Ignore me. This class is a secret handshake between RxUI and RxUI.Xaml
- /// in order to register certain classes on startup that would be difficult
- /// to register otherwise.
- ///
- public class PlatformRegistrations : IWantsToRegisterStuff
- {
- public void Register(Action, Type> registerFunction)
- {
- RxApp.MainThreadScheduler = new SynchronizationContextScheduler(SynchronizationContext.Current);
- }
- }
-}
diff --git a/src/Avalonia.ReactiveUI/Shims.cs b/src/Avalonia.ReactiveUI/Shims.cs
deleted file mode 100644
index 2729a68d6f..0000000000
--- a/src/Avalonia.ReactiveUI/Shims.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace System.Runtime.Serialization
-{
- class IgnoreDataMemberAttribute : Attribute
- {
- }
-
- class DataMemberAttribute : Attribute
- {
- }
- class OnDeserializedAttribute : Attribute
- {
- }
-
- class DataContractAttribute : Attribute
- {
- }
-
- class StreamingContext { }
-}
-
-namespace System.Diagnostics.Contracts
-{
- static class Contract
- {
- public static void Requires(bool condition)
- {
-
- }
- }
-}
diff --git a/src/Avalonia.ReactiveUI/src b/src/Avalonia.ReactiveUI/src
deleted file mode 160000
index 3f725c808b..0000000000
--- a/src/Avalonia.ReactiveUI/src
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 3f725c808b1d4c8457f0d3204e0a071aa462cd75
diff --git a/src/Avalonia.Styling/Avalonia.Styling.csproj b/src/Avalonia.Styling/Avalonia.Styling.csproj
index 965b6d87e6..6c0b957bf4 100644
--- a/src/Avalonia.Styling/Avalonia.Styling.csproj
+++ b/src/Avalonia.Styling/Avalonia.Styling.csproj
@@ -2,6 +2,7 @@
netstandard2.0
false
+ Avalonia
true
diff --git a/src/Avalonia.Styling/Controls/IResourceDictionary.cs b/src/Avalonia.Styling/Controls/IResourceDictionary.cs
new file mode 100644
index 0000000000..d7c86b7d74
--- /dev/null
+++ b/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
+{
+ ///
+ /// An indexed dictionary of resources.
+ ///
+ public interface IResourceDictionary : IResourceProvider, IDictionary
+ {
+ ///
+ /// Gets a collection of child resource dictionaries.
+ ///
+ IList MergedDictionaries { get; }
+ }
+}
diff --git a/src/Avalonia.Styling/Controls/IResourceNode.cs b/src/Avalonia.Styling/Controls/IResourceNode.cs
new file mode 100644
index 0000000000..b6a6cdc3e3
--- /dev/null
+++ b/src/Avalonia.Styling/Controls/IResourceNode.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace Avalonia.Controls
+{
+ ///
+ /// Represents resource provider in a tree.
+ ///
+ public interface IResourceNode : IResourceProvider
+ {
+ ///
+ /// Gets the parent resource node, if any.
+ ///
+ IResourceNode ResourceParent { get; }
+ }
+}
diff --git a/src/Avalonia.Styling/Controls/IResourceProvider.cs b/src/Avalonia.Styling/Controls/IResourceProvider.cs
new file mode 100644
index 0000000000..eec783623c
--- /dev/null
+++ b/src/Avalonia.Styling/Controls/IResourceProvider.cs
@@ -0,0 +1,33 @@
+using System;
+
+namespace Avalonia.Controls
+{
+ ///
+ /// Represents an object that can be queried for resources.
+ ///
+ public interface IResourceProvider
+ {
+ ///
+ /// Raised when resources in the provider are changed.
+ ///
+ event EventHandler ResourcesChanged;
+
+ ///
+ /// Gets a value indicating whether the element has resources.
+ ///
+ bool HasResources { get; }
+
+ ///
+ /// Tries to find a resource within the provider.
+ ///
+ /// The resource key.
+ ///
+ /// When this method returns, contains the value associated with the specified key,
+ /// if the key is found; otherwise, null.
+ ///
+ ///
+ /// True if the resource if found, otherwise false.
+ ///
+ bool TryGetResource(string key, out object value);
+ }
+}
diff --git a/src/Avalonia.Styling/Controls/ResourceDictionary.cs b/src/Avalonia.Styling/Controls/ResourceDictionary.cs
new file mode 100644
index 0000000000..74a861b36b
--- /dev/null
+++ b/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
+{
+ ///
+ /// An indexed dictionary of resources.
+ ///
+ public class ResourceDictionary : AvaloniaDictionary, IResourceDictionary
+ {
+ private AvaloniaList _mergedDictionaries;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ResourceDictionary()
+ {
+ CollectionChanged += OnCollectionChanged;
+ }
+
+ ///
+ public event EventHandler ResourcesChanged;
+
+ ///
+ public IList MergedDictionaries
+ {
+ get
+ {
+ if (_mergedDictionaries == null)
+ {
+ _mergedDictionaries = new AvaloniaList();
+ _mergedDictionaries.ResetBehavior = ResetBehavior.Remove;
+ _mergedDictionaries.ForEachItem(
+ x =>
+ {
+ if (x.HasResources)
+ {
+ OnResourcesChanged();
+ }
+
+ x.ResourcesChanged += MergedDictionaryResourcesChanged;
+ },
+ x =>
+ {
+ if (x.HasResources)
+ {
+ OnResourcesChanged();
+ }
+
+ x.ResourcesChanged -= MergedDictionaryResourcesChanged;
+ },
+ () => { });
+ }
+
+ return _mergedDictionaries;
+ }
+ }
+
+ ///
+ bool IResourceProvider.HasResources
+ {
+ get => Count > 0 || (_mergedDictionaries?.Any(x => x.HasResources) ?? false);
+ }
+
+ ///
+ 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();
+ }
+}
diff --git a/src/Avalonia.Styling/Controls/ResourceProviderExtensions.cs b/src/Avalonia.Styling/Controls/ResourceProviderExtensions.cs
new file mode 100644
index 0000000000..1f25fa132d
--- /dev/null
+++ b/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
+ {
+ ///
+ /// Finds the specified resource by searching up the logical tree and then global styles.
+ ///
+ /// The control.
+ /// The resource key.
+ /// The resource, or if not found.
+ public static object FindResource(this IResourceNode control, string key)
+ {
+ if (control.TryFindResource(key, out var value))
+ {
+ return value;
+ }
+
+ return AvaloniaProperty.UnsetValue;
+ }
+
+ ///
+ /// Tries to the specified resource by searching up the logical tree and then global styles.
+ ///
+ /// The control.
+ /// The resource key.
+ /// On return, contains the resource if found, otherwise null.
+ /// True if the resource was found; otherwise false.
+ public static bool TryFindResource(this IResourceNode control, string key, out object value)
+ {
+ Contract.Requires(control != null);
+ Contract.Requires(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 GetResourceObservable(this IResourceNode target, string key)
+ {
+ return Observable.FromEventPattern(
+ x => target.ResourcesChanged += x,
+ x => target.ResourcesChanged -= x)
+ .StartWith((EventPattern)null)
+ .Select(x => target.FindResource(key));
+ }
+ }
+}
diff --git a/src/Avalonia.Styling/Controls/ResourcesChangedEventArgs.cs b/src/Avalonia.Styling/Controls/ResourcesChangedEventArgs.cs
new file mode 100644
index 0000000000..68c7a58ab9
--- /dev/null
+++ b/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
+ {
+ }
+}
diff --git a/src/Avalonia.Styling/LogicalTree/ILogical.cs b/src/Avalonia.Styling/LogicalTree/ILogical.cs
index 006a9f5cc1..8ee3c9ea4f 100644
--- a/src/Avalonia.Styling/LogicalTree/ILogical.cs
+++ b/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.
///
void NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e);
+
+ ///
+ /// Notifies the control that a change has been made to resources that apply to it.
+ ///
+ /// The event args.
+ ///
+ /// This method will be called automatically by the framework, you should not need to call
+ /// this method yourself.
+ ///
+ void NotifyResourcesChanged(ResourcesChangedEventArgs e);
}
}
diff --git a/src/Avalonia.Styling/Properties/AssemblyInfo.cs b/src/Avalonia.Styling/Properties/AssemblyInfo.cs
index b53681aeed..0a639139f7 100644
--- a/src/Avalonia.Styling/Properties/AssemblyInfo.cs
+++ b/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")]
\ No newline at end of file
diff --git a/src/Avalonia.Styling/Styling/ISetStyleParent.cs b/src/Avalonia.Styling/Styling/ISetStyleParent.cs
new file mode 100644
index 0000000000..9f5855b401
--- /dev/null
+++ b/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
+{
+ ///
+ /// Defines an interface through which a 's parent can be set.
+ ///
+ ///
+ /// You should not usually need to use this interface - it is for internal use only.
+ ///
+ public interface ISetStyleParent : IStyle
+ {
+ ///
+ /// Sets the style parent.
+ ///
+ /// The parent.
+ void SetParent(IResourceNode parent);
+
+ ///
+ /// Notifies the style that a change has been made to resources that apply to it.
+ ///
+ /// The event args.
+ ///
+ /// This method will be called automatically by the framework, you should not need to call
+ /// this method yourself.
+ ///
+ void NotifyResourcesChanged(ResourcesChangedEventArgs e);
+ }
+}
diff --git a/src/Avalonia.Styling/Styling/IStyle.cs b/src/Avalonia.Styling/Styling/IStyle.cs
index 7b8510fe2d..5f12763825 100644
--- a/src/Avalonia.Styling/Styling/IStyle.cs
+++ b/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
{
///
/// Defines the interface for styles.
///
- public interface IStyle
+ public interface IStyle : IResourceNode
{
///
/// 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.
///
void Attach(IStyleable control, IStyleHost container);
-
- ///
- /// Tries to find a named resource within the style.
- ///
- /// The resource name.
- ///
- /// The resource if found, otherwise .
- ///
- object FindResource(string name);
}
}
diff --git a/src/Avalonia.Styling/Styling/Style.cs b/src/Avalonia.Styling/Styling/Style.cs
index 3dfd9118af..4182ffada3 100644
--- a/src/Avalonia.Styling/Styling/Style.cs
+++ b/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
///
/// Defines a style.
///
- public class Style : IStyle
+ public class Style : IStyle, ISetStyleParent
{
private static Dictionary> _applied =
new Dictionary>();
-
- private StyleResources _resources;
+ private IResourceNode _parent;
+ private IResourceDictionary _resources;
///
/// Initializes a new instance of the class.
@@ -34,33 +36,33 @@ namespace Avalonia.Styling
Selector = selector(null);
}
+ ///
+ public event EventHandler ResourcesChanged;
+
///
/// Gets or sets a dictionary of style resources.
///
- public StyleResources Resources
+ public IResourceDictionary Resources
{
- get
+ get => _resources ?? (Resources = new ResourceDictionary());
+ set
{
- if (_resources == null)
+ Contract.Requires(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).Add(i);
- }
+ ((ISetStyleParent)this).NotifyResourcesChanged(new ResourcesChangedEventArgs());
}
}
}
@@ -76,6 +78,12 @@ namespace Avalonia.Styling
[Content]
public IList Setters { get; set; } = new List();
+ ///
+ IResourceNode IResourceNode.ResourceParent => _parent;
+
+ ///
+ bool IResourceProvider.HasResources => _resources?.Count > 0;
+
///
/// Attaches the style to a control if the style's selector matches.
///
@@ -112,25 +120,11 @@ namespace Avalonia.Styling
}
}
- ///
- /// Tries to find a named resource within the style.
- ///
- /// The resource name.
- ///
- /// The resource if found, otherwise .
- ///
- public object FindResource(string name)
+ ///
+ 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;
}
///
@@ -149,6 +143,23 @@ namespace Avalonia.Styling
}
}
+ ///
+ void ISetStyleParent.NotifyResourcesChanged(ResourcesChangedEventArgs e)
+ {
+ ResourcesChanged?.Invoke(this, e);
+ }
+
+ ///
+ void ISetStyleParent.SetParent(IResourceNode parent)
+ {
+ if (_parent != null && parent != null)
+ {
+ throw new InvalidOperationException("The Style already has a parent.");
+ }
+
+ _parent = parent;
+ }
+
private static List GetSubscriptions(IStyleable control)
{
List subscriptions;
@@ -179,5 +190,10 @@ namespace Avalonia.Styling
_applied.Remove(control);
}
+
+ private void ResourceDictionaryChanged(object sender, ResourcesChangedEventArgs e)
+ {
+ ResourcesChanged?.Invoke(this, e);
+ }
}
}
diff --git a/src/Avalonia.Styling/Styling/StyleExtensions.cs b/src/Avalonia.Styling/Styling/StyleExtensions.cs
deleted file mode 100644
index d53d00aed3..0000000000
--- a/src/Avalonia.Styling/Styling/StyleExtensions.cs
+++ /dev/null
@@ -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
- {
- ///
- /// Tries to find a named style resource.
- ///
- /// The control from which to find the resource.
- /// The resource name.
- ///
- /// The resource if found, otherwise .
- ///
- public static object FindStyleResource(this IStyleHost control, string name)
- {
- Contract.Requires(control != null);
- Contract.Requires(name != null);
- Contract.Requires(!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;
- }
- }
-}
diff --git a/src/Avalonia.Styling/Styling/StyleResources.cs b/src/Avalonia.Styling/Styling/StyleResources.cs
deleted file mode 100644
index e447c6adfd..0000000000
--- a/src/Avalonia.Styling/Styling/StyleResources.cs
+++ /dev/null
@@ -1,90 +0,0 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-
-namespace Avalonia.Styling
-{
- ///
- /// Holds resources for a .
- ///
- public class StyleResources : IDictionary, IDictionary
- {
- private Dictionary _inner = new Dictionary();
-
- public object this[string key]
- {
- get { return _inner[key]; }
- set { _inner[key] = value; }
- }
-
- public int Count => _inner.Count;
-
- ICollection IDictionary.Keys => _inner.Keys;
-
- ICollection IDictionary.Values => _inner.Values;
-
- bool ICollection>.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> GetEnumerator() => _inner.GetEnumerator();
-
- public bool TryGetValue(string key, out object value) => _inner.TryGetValue(key, out value);
-
- bool ICollection>.Contains(KeyValuePair item)
- {
- return ((IDictionary)_inner).Contains(item);
- }
-
- void ICollection>.Add(KeyValuePair item)
- {
- ((IDictionary)_inner).Add(item);
- }
-
- void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex)
- {
- ((IDictionary)_inner).CopyTo(array, arrayIndex);
- }
-
- bool ICollection>.Remove(KeyValuePair item)
- {
- return ((IDictionary)_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);
- }
-}
diff --git a/src/Avalonia.Styling/Styling/Styles.cs b/src/Avalonia.Styling/Styling/Styles.cs
index 770ef8344f..714e7f6def 100644
--- a/src/Avalonia.Styling/Styling/Styles.cs
+++ b/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
{
///
/// A style that consists of a number of child styles.
///
- public class Styles : AvaloniaList, IStyle
+ public class Styles : AvaloniaList, 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;
+ },
+ () => { });
+ }
+
+ ///
+ public event EventHandler ResourcesChanged;
+
+ ///
+ public bool HasResources => _resources?.Count > 0 || this.Any(x => x.HasResources);
+
+ ///
+ /// Gets or sets a dictionary of style resources.
+ ///
+ public IResourceDictionary Resources
+ {
+ get => _resources ?? (Resources = new ResourceDictionary());
+ set
+ {
+ Contract.Requires(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());
+ }
+ }
+ }
+
+ ///
+ IResourceNode IResourceNode.ResourceParent => _parent;
+
///
/// Attaches the style to a control if the style's selector matches.
///
@@ -26,26 +105,68 @@ namespace Avalonia.Styling
}
}
- ///
- /// Tries to find a named resource within the style.
- ///
- /// The resource name.
- ///
- /// The resource if found, otherwise .
- ///
- public object FindResource(string name)
+ ///
+ 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;
+ }
+
+ ///
+ void ISetStyleParent.SetParent(IResourceNode parent)
+ {
+ if (_parent != null && parent != null)
+ {
+ throw new InvalidOperationException("The Style already has a parent.");
+ }
+
+ _parent = parent;
+ }
+
+ ///
+ 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);
}
}
}
diff --git a/src/Avalonia.Themes.Default/Button.xaml b/src/Avalonia.Themes.Default/Button.xaml
index daa2973b21..908f293fa7 100644
--- a/src/Avalonia.Themes.Default/Button.xaml
+++ b/src/Avalonia.Themes.Default/Button.xaml
@@ -1,9 +1,9 @@
\ No newline at end of file
diff --git a/src/Avalonia.Themes.Default/CheckBox.xaml b/src/Avalonia.Themes.Default/CheckBox.xaml
index 2fc19b2679..195ec098c9 100644
--- a/src/Avalonia.Themes.Default/CheckBox.xaml
+++ b/src/Avalonia.Themes.Default/CheckBox.xaml
@@ -1,8 +1,8 @@
\ No newline at end of file
diff --git a/src/Avalonia.Themes.Default/DefaultTheme.xaml b/src/Avalonia.Themes.Default/DefaultTheme.xaml
index 07a3a6f429..48f530cca8 100644
--- a/src/Avalonia.Themes.Default/DefaultTheme.xaml
+++ b/src/Avalonia.Themes.Default/DefaultTheme.xaml
@@ -20,6 +20,7 @@
+
diff --git a/src/Avalonia.Themes.Default/DropDown.xaml b/src/Avalonia.Themes.Default/DropDown.xaml
index 5a3d44360c..0e8b409cfd 100644
--- a/src/Avalonia.Themes.Default/DropDown.xaml
+++ b/src/Avalonia.Themes.Default/DropDown.xaml
@@ -1,7 +1,7 @@
\ No newline at end of file
diff --git a/src/Avalonia.Themes.Default/EmbeddableControlRoot.xaml b/src/Avalonia.Themes.Default/EmbeddableControlRoot.xaml
index edea0ddf64..f39720bb65 100644
--- a/src/Avalonia.Themes.Default/EmbeddableControlRoot.xaml
+++ b/src/Avalonia.Themes.Default/EmbeddableControlRoot.xaml
@@ -1,7 +1,7 @@
@@ -25,7 +25,7 @@
diff --git a/src/Avalonia.Themes.Default/ListBox.xaml b/src/Avalonia.Themes.Default/ListBox.xaml
index 5aa23f5275..aa63a1b6c3 100644
--- a/src/Avalonia.Themes.Default/ListBox.xaml
+++ b/src/Avalonia.Themes.Default/ListBox.xaml
@@ -1,7 +1,7 @@
\ No newline at end of file
diff --git a/src/Avalonia.Themes.Default/RepeatButton.xaml b/src/Avalonia.Themes.Default/RepeatButton.xaml
new file mode 100644
index 0000000000..557e2d3b03
--- /dev/null
+++ b/src/Avalonia.Themes.Default/RepeatButton.xaml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Avalonia.Themes.Default/ScrollBar.xaml b/src/Avalonia.Themes.Default/ScrollBar.xaml
index 89f6f26c08..d270384123 100644
--- a/src/Avalonia.Themes.Default/ScrollBar.xaml
+++ b/src/Avalonia.Themes.Default/ScrollBar.xaml
@@ -1,35 +1,127 @@
-
-
-
-
-
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Avalonia.Themes.Default/ScrollViewer.xaml b/src/Avalonia.Themes.Default/ScrollViewer.xaml
index 4adfdaae2d..c7f9ad31db 100644
--- a/src/Avalonia.Themes.Default/ScrollViewer.xaml
+++ b/src/Avalonia.Themes.Default/ScrollViewer.xaml
@@ -1,4 +1,6 @@
diff --git a/src/Avalonia.Themes.Default/Slider.xaml b/src/Avalonia.Themes.Default/Slider.xaml
index 4fa293fbc5..0944612417 100644
--- a/src/Avalonia.Themes.Default/Slider.xaml
+++ b/src/Avalonia.Themes.Default/Slider.xaml
@@ -12,11 +12,19 @@