diff --git a/Perspex.Controls/Button.cs b/Perspex.Controls/Button.cs index 5e31bb8298..494fd5a983 100644 --- a/Perspex.Controls/Button.cs +++ b/Perspex.Controls/Button.cs @@ -22,7 +22,7 @@ namespace Perspex.Controls PerspexProperty.Register("ClickMode"); public static readonly RoutedEvent ClickEvent = - RoutedEvent.Register("Click", RoutingStrategy.Bubble); + RoutedEvent.Register("Click", RoutingStrategies.Bubble); static Button() { diff --git a/Perspex.Controls/Control.cs b/Perspex.Controls/Control.cs index 32dcf64955..73a9db0f03 100644 --- a/Perspex.Controls/Control.cs +++ b/Perspex.Controls/Control.cs @@ -30,7 +30,7 @@ namespace Perspex.Controls PerspexProperty.Register("TemplatedParent"); public static readonly RoutedEvent RequestBringIntoViewEvent = - RoutedEvent.Register("RequestBringIntoView", RoutingStrategy.Bubble); + RoutedEvent.Register("RequestBringIntoView", RoutingStrategies.Bubble); private static readonly IPerspexReadOnlyList EmptyChildren = new PerspexSingleItemList(); diff --git a/Perspex.Controls/Primitives/Thumb.cs b/Perspex.Controls/Primitives/Thumb.cs index 0f97bff2eb..64ffaa7535 100644 --- a/Perspex.Controls/Primitives/Thumb.cs +++ b/Perspex.Controls/Primitives/Thumb.cs @@ -13,13 +13,13 @@ namespace Perspex.Controls.Primitives public class Thumb : TemplatedControl { public static readonly RoutedEvent DragStartedEvent = - RoutedEvent.Register("DragStarted", RoutingStrategy.Bubble); + RoutedEvent.Register("DragStarted", RoutingStrategies.Bubble); public static readonly RoutedEvent DragDeltaEvent = - RoutedEvent.Register("DragDelta", RoutingStrategy.Bubble); + RoutedEvent.Register("DragDelta", RoutingStrategies.Bubble); public static readonly RoutedEvent DragCompletedEvent = - RoutedEvent.Register("DragCompleted", RoutingStrategy.Bubble); + RoutedEvent.Register("DragCompleted", RoutingStrategies.Bubble); private Point? lastPoint; diff --git a/Perspex.Input/InputElement.cs b/Perspex.Input/InputElement.cs index ec0a403ab9..7730b6eadd 100644 --- a/Perspex.Input/InputElement.cs +++ b/Perspex.Input/InputElement.cs @@ -30,37 +30,37 @@ namespace Perspex.Input PerspexProperty.Register("IsPointerOver"); public static readonly RoutedEvent GotFocusEvent = - RoutedEvent.Register("GotFocus", RoutingStrategy.Bubble); + RoutedEvent.Register("GotFocus", RoutingStrategies.Bubble); public static readonly RoutedEvent LostFocusEvent = - RoutedEvent.Register("LostFocus", RoutingStrategy.Bubble); + RoutedEvent.Register("LostFocus", RoutingStrategies.Bubble); public static readonly RoutedEvent KeyDownEvent = - RoutedEvent.Register("KeyDown", RoutingStrategy.Bubble); + RoutedEvent.Register("KeyDown", RoutingStrategies.Bubble); public static readonly RoutedEvent PreviewKeyDownEvent = - RoutedEvent.Register("PreviewKeyDown", RoutingStrategy.Tunnel); + RoutedEvent.Register("PreviewKeyDown", RoutingStrategies.Tunnel); public static readonly RoutedEvent PreviewPointerPressedEvent = - RoutedEvent.Register("PreviewPointerPressed", RoutingStrategy.Tunnel); + RoutedEvent.Register("PreviewPointerPressed", RoutingStrategies.Tunnel); public static readonly RoutedEvent PointerEnterEvent = - RoutedEvent.Register("PointerEnter", RoutingStrategy.Direct); + RoutedEvent.Register("PointerEnter", RoutingStrategies.Direct); public static readonly RoutedEvent PointerLeaveEvent = - RoutedEvent.Register("PointerLeave", RoutingStrategy.Direct); + RoutedEvent.Register("PointerLeave", RoutingStrategies.Direct); public static readonly RoutedEvent PointerMovedEvent = - RoutedEvent.Register("PointerMove", RoutingStrategy.Bubble); + RoutedEvent.Register("PointerMove", RoutingStrategies.Bubble); public static readonly RoutedEvent PointerPressedEvent = - RoutedEvent.Register("PointerPressed", RoutingStrategy.Bubble); + RoutedEvent.Register("PointerPressed", RoutingStrategies.Bubble); public static readonly RoutedEvent PointerReleasedEvent = - RoutedEvent.Register("PointerReleased", RoutingStrategy.Bubble); + RoutedEvent.Register("PointerReleased", RoutingStrategies.Bubble); public static readonly RoutedEvent PointerWheelChangedEvent = - RoutedEvent.Register("PointerWheelChanged", RoutingStrategy.Bubble); + RoutedEvent.Register("PointerWheelChanged", RoutingStrategies.Bubble); static InputElement() { diff --git a/Perspex.Interactive.UnitTests/InteractiveTests.cs b/Perspex.Interactive.UnitTests/InteractiveTests.cs new file mode 100644 index 0000000000..9e9610c1f9 --- /dev/null +++ b/Perspex.Interactive.UnitTests/InteractiveTests.cs @@ -0,0 +1,251 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Interactive.UnitTests +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Perspex.Collections; + using Perspex.Interactivity; + using Perspex.VisualTree; + using Xunit; + + public class InteractiveTests + { + [Fact] + public void Direct_Event_Should_Go_Straight_To_Source() + { + var ev = new RoutedEvent("test", RoutingStrategies.Direct, typeof(RoutedEventArgs), typeof(TestInteractive)); + var invoked = new List(); + EventHandler handler = (s, e) => invoked.Add(((TestInteractive)s).Id); + var target = this.CreateTree(ev, handler, RoutingStrategies.Direct); + + var args = new RoutedEventArgs(ev, target); + target.RaiseEvent(args); + + Assert.Equal(new[] { "2b" }, invoked); + } + + [Fact] + public void Direct_Event_Should_Have_Route_Set_To_Direct() + { + var ev = new RoutedEvent("test", RoutingStrategies.Direct, typeof(RoutedEventArgs), typeof(TestInteractive)); + bool called = false; + + EventHandler handler = (s, e) => + { + Assert.Equal(RoutingStrategies.Direct, e.Route); + called = true; + }; + + var target = this.CreateTree(ev, handler, RoutingStrategies.Direct); + + var args = new RoutedEventArgs(ev, target); + target.RaiseEvent(args); + + Assert.True(called); + } + + [Fact] + public void Bubbling_Event_Should_Bubble_Up() + { + var ev = new RoutedEvent("test", RoutingStrategies.Bubble, typeof(RoutedEventArgs), typeof(TestInteractive)); + var invoked = new List(); + EventHandler handler = (s, e) => invoked.Add(((TestInteractive)s).Id); + var target = this.CreateTree(ev, handler, RoutingStrategies.Bubble | RoutingStrategies.Tunnel); + + var args = new RoutedEventArgs(ev, target); + target.RaiseEvent(args); + + Assert.Equal(new[] { "2b", "1" }, invoked); + } + + [Fact] + public void Tunneling_Event_Should_Tunnel() + { + var ev = new RoutedEvent("test", RoutingStrategies.Tunnel, typeof(RoutedEventArgs), typeof(TestInteractive)); + var invoked = new List(); + EventHandler handler = (s, e) => invoked.Add(((TestInteractive)s).Id); + var target = this.CreateTree(ev, handler, RoutingStrategies.Bubble | RoutingStrategies.Tunnel); + + var args = new RoutedEventArgs(ev, target); + target.RaiseEvent(args); + + Assert.Equal(new[] { "1", "2b" }, invoked); + } + + [Fact] + public void Tunneling_Bubbling_Event_Should_Tunnel_Then_Bubble_Up() + { + var ev = new RoutedEvent( + "test", + RoutingStrategies.Bubble | RoutingStrategies.Tunnel, + typeof(RoutedEventArgs), + typeof(TestInteractive)); + var invoked = new List(); + EventHandler handler = (s, e) => invoked.Add(((TestInteractive)s).Id); + var target = this.CreateTree(ev, handler, RoutingStrategies.Bubble | RoutingStrategies.Tunnel); + + var args = new RoutedEventArgs(ev, target); + target.RaiseEvent(args); + + Assert.Equal(new[] { "1", "2b", "2b", "1" }, invoked); + } + + [Fact] + public void Events_Should_Have_Route_Set() + { + var ev = new RoutedEvent( + "test", + RoutingStrategies.Bubble | RoutingStrategies.Tunnel, + typeof(RoutedEventArgs), + typeof(TestInteractive)); + var invoked = new List(); + EventHandler handler = (s, e) => invoked.Add(e.Route); + var target = this.CreateTree(ev, handler, RoutingStrategies.Bubble | RoutingStrategies.Tunnel); + + var args = new RoutedEventArgs(ev, target); + target.RaiseEvent(args); + + Assert.Equal(new[] + { + RoutingStrategies.Tunnel, + RoutingStrategies.Tunnel, + RoutingStrategies.Bubble, + RoutingStrategies.Bubble, + }, + invoked); + } + + [Fact] + public void Direct_Subscription_Should_Not_Catch_Tunneling_Or_Bubbling() + { + var ev = new RoutedEvent( + "test", + RoutingStrategies.Bubble | RoutingStrategies.Tunnel, + typeof(RoutedEventArgs), + typeof(TestInteractive)); + var count = 0; + + EventHandler handler = (s, e) => + { + ++count; + }; + + var target = this.CreateTree(ev, handler, RoutingStrategies.Direct); + + var args = new RoutedEventArgs(ev, target); + target.RaiseEvent(args); + + Assert.Equal(0, count); + } + + [Fact] + public void Bubbling_Subscription_Should_Not_Catch_Tunneling() + { + var ev = new RoutedEvent( + "test", + RoutingStrategies.Bubble | RoutingStrategies.Tunnel, + typeof(RoutedEventArgs), + typeof(TestInteractive)); + var count = 0; + + EventHandler handler = (s, e) => + { + Assert.Equal(RoutingStrategies.Bubble, e.Route); + ++count; + }; + + var target = this.CreateTree(ev, handler, RoutingStrategies.Bubble); + + var args = new RoutedEventArgs(ev, target); + target.RaiseEvent(args); + + Assert.Equal(2, count); + } + + [Fact] + public void Tunneling_Subscription_Should_Not_Catch_Bubbling() + { + var ev = new RoutedEvent( + "test", + RoutingStrategies.Bubble | RoutingStrategies.Tunnel, + typeof(RoutedEventArgs), + typeof(TestInteractive)); + var count = 0; + + EventHandler handler = (s, e) => + { + Assert.Equal(RoutingStrategies.Tunnel, e.Route); + ++count; + }; + + var target = this.CreateTree(ev, handler, RoutingStrategies.Tunnel); + + var args = new RoutedEventArgs(ev, target); + target.RaiseEvent(args); + + Assert.Equal(2, count); + } + + private TestInteractive CreateTree( + RoutedEvent ev, + EventHandler handler, + RoutingStrategies handlerRoutes) + { + TestInteractive target; + + var tree = new TestInteractive + { + Id = "1", + Children = new[] + { + new TestInteractive + { + Id = "2a", + }, + (target = new TestInteractive + { + Id = "2b", + Children = new[] + { + new TestInteractive + { + Id = "3", + }, + }, + }), + } + }; + + foreach (var i in tree.GetSelfAndVisualDescendents().Cast()) + { + i.AddHandler(ev, handler, handlerRoutes, false); + } + + return target; + } + + private class TestInteractive : Interactive + { + public string Id { get; set; } + + public IEnumerable Children + { + get + { + return ((IVisual)this).VisualChildren.AsEnumerable(); + } + + set + { + this.AddVisualChildren(value.Cast()); + } + } + } + } +} diff --git a/Perspex.Interactive.UnitTests/Perspex.Interactive.UnitTests.csproj b/Perspex.Interactive.UnitTests/Perspex.Interactive.UnitTests.csproj new file mode 100644 index 0000000000..d76f750f74 --- /dev/null +++ b/Perspex.Interactive.UnitTests/Perspex.Interactive.UnitTests.csproj @@ -0,0 +1,99 @@ + + + + + + Debug + AnyCPU + {08478EF5-44E8-42E9-92D6-15E00EC038D8} + Library + Properties + Perspex.Interactive.UnitTests + Perspex.Interactive.UnitTests + v4.5 + 512 + ff1a0665 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Splat.1.6.0\lib\Net45\Splat.dll + + + + + + + + + + ..\packages\xunit.1.9.2\lib\net20\xunit.dll + + + + + + + + + + + + {415e048e-4611-4815-9cf2-d774e29079ac} + NGenerics + + + {d211e587-d8bc-45b9-95a4-f297c8fa5200} + Perspex.Animation + + + {b09b78d8-9b26-48b0-9149-d64a2f120f3f} + Perspex.Base + + + {6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b} + Perspex.Interactivity + + + {42472427-4774-4c81-8aff-9f27b8e31721} + Perspex.Layout + + + {eb582467-6abb-43a1-b052-e981ba910e3a} + Perspex.SceneGraph + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/Perspex.Interactive.UnitTests/Properties/AssemblyInfo.cs b/Perspex.Interactive.UnitTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..a3fb89a3cf --- /dev/null +++ b/Perspex.Interactive.UnitTests/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +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("Perspex.Interactive.UnitTests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Perspex.Interactive.UnitTests")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// 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("08478ef5-44e8-42e9-92d6-15e00ec038d8")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Perspex.Interactive.UnitTests/packages.config b/Perspex.Interactive.UnitTests/packages.config new file mode 100644 index 0000000000..b62b116d38 --- /dev/null +++ b/Perspex.Interactive.UnitTests/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Perspex.Interactivity/Interactive.cs b/Perspex.Interactivity/Interactive.cs index 064074c6ed..e8e15a9cc7 100644 --- a/Perspex.Interactivity/Interactive.cs +++ b/Perspex.Interactivity/Interactive.cs @@ -16,22 +16,32 @@ namespace Perspex.Interactivity public class Interactive : Layoutable, IInteractive { - private Dictionary> eventHandlers = new Dictionary>(); - - public void AddHandler(RoutedEvent routedEvent, Delegate handler) + private Dictionary> eventHandlers = + new Dictionary>(); + + public void AddHandler( + RoutedEvent routedEvent, + Delegate handler, + RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble, + bool handledEventsToo = false) { Contract.Requires(routedEvent != null); Contract.Requires(handler != null); - List delegates; + List subscriptions; - if (!this.eventHandlers.TryGetValue(routedEvent, out delegates)) + if (!this.eventHandlers.TryGetValue(routedEvent, out subscriptions)) { - delegates = new List(); - this.eventHandlers.Add(routedEvent, delegates); + subscriptions = new List(); + this.eventHandlers.Add(routedEvent, subscriptions); } - delegates.Add(handler); + subscriptions.Add(new Subscription + { + Handler = handler, + Routes = routes, + AlsoIfHandled = handledEventsToo, + }); } public IObservable> GetObservable(RoutedEvent routedEvent) where T : RoutedEventArgs @@ -48,11 +58,11 @@ namespace Perspex.Interactivity Contract.Requires(routedEvent != null); Contract.Requires(handler != null); - List delegates; + List subscriptions; - if (this.eventHandlers.TryGetValue(routedEvent, out delegates)) + if (this.eventHandlers.TryGetValue(routedEvent, out subscriptions)) { - delegates.Remove(handler); + subscriptions.RemoveAll(x => x.Handler == handler); } } @@ -65,17 +75,20 @@ namespace Perspex.Interactivity if (!e.Handled) { - switch (e.RoutedEvent.RoutingStrategy) + if (e.RoutedEvent.RoutingStrategies == RoutingStrategies.Direct) + { + e.Route = RoutingStrategies.Direct; + this.RaiseEventImpl(e); + } + + if ((e.RoutedEvent.RoutingStrategies & RoutingStrategies.Tunnel) != 0) + { + this.TunnelEvent(e); + } + + if ((e.RoutedEvent.RoutingStrategies & RoutingStrategies.Bubble) != 0) { - case RoutingStrategy.Bubble: - this.BubbleEvent(e); - break; - case RoutingStrategy.Direct: - this.RaiseEventImpl(e); - break; - case RoutingStrategy.Tunnel: - this.TunnelEvent(e); - break; + this.BubbleEvent(e); } } } @@ -84,6 +97,8 @@ namespace Perspex.Interactivity { Contract.Requires(e != null); + e.Route = RoutingStrategies.Bubble; + foreach (var target in this.GetSelfAndVisualAncestors().OfType()) { target.RaiseEventImpl(e); @@ -99,6 +114,8 @@ namespace Perspex.Interactivity { Contract.Requires(e != null); + e.Route = RoutingStrategies.Tunnel; + foreach (var target in this.GetSelfAndVisualAncestors().OfType().Reverse()) { target.RaiseEventImpl(e); @@ -114,17 +131,33 @@ namespace Perspex.Interactivity { Contract.Requires(e != null); - List delegates; + List subscriptions; e.RoutedEvent.InvokeRaised(this, e); - if (this.eventHandlers.TryGetValue(e.RoutedEvent, out delegates)) + if (this.eventHandlers.TryGetValue(e.RoutedEvent, out subscriptions)) { - foreach (Delegate handler in delegates.ToList()) + foreach (var sub in subscriptions) { - handler.DynamicInvoke(this, e); + bool invoke = + (e.Route == RoutingStrategies.Direct && sub.Routes == RoutingStrategies.Direct) || + (e.Route != RoutingStrategies.Direct && (e.Route & sub.Routes) != 0); + + if (invoke) + { + sub.Handler.DynamicInvoke(this, e); + } } } } + + private class Subscription + { + public Delegate Handler { get; set; } + + public RoutingStrategies Routes { get; set; } + + public bool AlsoIfHandled { get; set; } + } } } diff --git a/Perspex.Interactivity/RoutedEvent.cs b/Perspex.Interactivity/RoutedEvent.cs index 1a9d34728a..5be4681433 100644 --- a/Perspex.Interactivity/RoutedEvent.cs +++ b/Perspex.Interactivity/RoutedEvent.cs @@ -10,18 +10,19 @@ namespace Perspex.Interactivity using System.Linq.Expressions; using System.Reflection; - public enum RoutingStrategy + [Flags] + public enum RoutingStrategies { - Tunnel, - Bubble, - Direct, + Direct = 0, + Tunnel = 1, + Bubble = 2, } public class RoutedEvent { public RoutedEvent( string name, - RoutingStrategy routingStrategy, + RoutingStrategies routingStrategies, Type eventArgsType, Type ownerType) { @@ -34,7 +35,7 @@ namespace Perspex.Interactivity this.EventArgsType = eventArgsType; this.Name = name; this.OwnerType = ownerType; - this.RoutingStrategy = routingStrategy; + this.RoutingStrategies = routingStrategies; } public event EventHandler Raised; @@ -57,7 +58,7 @@ namespace Perspex.Interactivity private set; } - public RoutingStrategy RoutingStrategy + public RoutingStrategies RoutingStrategies { get; private set; @@ -65,7 +66,7 @@ namespace Perspex.Interactivity public static RoutedEvent Register( string name, - RoutingStrategy routingStrategy) + RoutingStrategies routingStrategy) where TOwner : IInteractive where TEventArgs : RoutedEventArgs { @@ -76,7 +77,7 @@ namespace Perspex.Interactivity public static RoutedEvent Register( string name, - RoutingStrategy routingStrategy, + RoutingStrategies routingStrategy, Type ownerType) where TEventArgs : RoutedEventArgs { @@ -97,8 +98,8 @@ namespace Perspex.Interactivity public class RoutedEvent : RoutedEvent where TEventArgs : RoutedEventArgs { - public RoutedEvent(string name, RoutingStrategy routingStrategy, Type ownerType) - : base(name, routingStrategy, typeof(TEventArgs), ownerType) + public RoutedEvent(string name, RoutingStrategies routingStrategies, Type ownerType) + : base(name, routingStrategies, typeof(TEventArgs), ownerType) { Contract.Requires(name != null); Contract.Requires(ownerType != null); diff --git a/Perspex.Interactivity/RoutedEventArgs.cs b/Perspex.Interactivity/RoutedEventArgs.cs index 9e54d1d585..a17c026e47 100644 --- a/Perspex.Interactivity/RoutedEventArgs.cs +++ b/Perspex.Interactivity/RoutedEventArgs.cs @@ -10,12 +10,24 @@ namespace Perspex.Interactivity public class RoutedEventArgs : EventArgs { + public RoutedEventArgs() + { + } + + public RoutedEventArgs(RoutedEvent routedEvent, IInteractive source) + { + this.RoutedEvent = routedEvent; + this.Source = this.OriginalSource = source; + } + public bool Handled { get; set; } public IInteractive OriginalSource { get; set; } public RoutedEvent RoutedEvent { get; set; } + public RoutingStrategies Route { get; set; } + public IInteractive Source { get; set; } } } diff --git a/Perspex.SceneGraph/VisualTree/VisualExtensions.cs b/Perspex.SceneGraph/VisualTree/VisualExtensions.cs index f21bc1ec96..c3b0bd239e 100644 --- a/Perspex.SceneGraph/VisualTree/VisualExtensions.cs +++ b/Perspex.SceneGraph/VisualTree/VisualExtensions.cs @@ -83,6 +83,16 @@ namespace Perspex.VisualTree } } + public static IEnumerable GetSelfAndVisualDescendents(this IVisual visual) + { + yield return visual; + + foreach (var ancestor in visual.GetVisualDescendents()) + { + yield return ancestor; + } + } + public static IVisual GetVisualParent(this IVisual visual) { return visual.VisualParent; diff --git a/Perspex.sln b/Perspex.sln index ba58827357..1eaef59be6 100644 --- a/Perspex.sln +++ b/Perspex.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2013 -VisualStudioVersion = 12.0.31101.0 +# Visual Studio 14 +VisualStudioVersion = 14.0.22512.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Win32", "Windows\Perspex.Win32\Perspex.Win32.csproj", "{811A76CF-1CF6-440F-963B-BBE31BD72A82}" EndProject @@ -63,6 +63,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{596AF7 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Interactive.UnitTests", "Perspex.Interactive.UnitTests\Perspex.Interactive.UnitTests.csproj", "{08478EF5-44E8-42E9-92D6-15E00EC038D8}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -164,6 +166,10 @@ Global {D211E587-D8BC-45B9-95A4-F297C8FA5200}.Debug|Any CPU.Build.0 = Debug|Any CPU {D211E587-D8BC-45B9-95A4-F297C8FA5200}.Release|Any CPU.ActiveCfg = Release|Any CPU {D211E587-D8BC-45B9-95A4-F297C8FA5200}.Release|Any CPU.Build.0 = Release|Any CPU + {08478EF5-44E8-42E9-92D6-15E00EC038D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {08478EF5-44E8-42E9-92D6-15E00EC038D8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {08478EF5-44E8-42E9-92D6-15E00EC038D8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {08478EF5-44E8-42E9-92D6-15E00EC038D8}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -180,5 +186,6 @@ Global {2905FF23-53FB-45E6-AA49-6AF47A172056} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {415E048E-4611-4815-9CF2-D774E29079AC} = {2BAFBE53-7FA4-4BB9-976F-9AFCC4F9847D} {DB070A10-BF39-4752-8456-86E9D5928478} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} + {08478EF5-44E8-42E9-92D6-15E00EC038D8} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} EndGlobalSection EndGlobal