// Copyright (c) The Perspex Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; using System.Reactive.Subjects; using System.Reflection; using System.Runtime.ExceptionServices; namespace Perspex.Interactivity { [Flags] public enum RoutingStrategies { Direct = 0x01, Tunnel = 0x02, Bubble = 0x04, } public class RoutedEvent { private Subject> _raised = new Subject>(); private Subject _routeFinished = new Subject(); public RoutedEvent( string name, RoutingStrategies routingStrategies, Type eventArgsType, Type ownerType) { Contract.Requires(name != null); Contract.Requires(eventArgsType != null); Contract.Requires(ownerType != null); Contract.Requires(typeof(RoutedEventArgs).GetTypeInfo().IsAssignableFrom(eventArgsType.GetTypeInfo())); EventArgsType = eventArgsType; Name = name; OwnerType = ownerType; RoutingStrategies = routingStrategies; } public Type EventArgsType { get; private set; } public string Name { get; private set; } public Type OwnerType { get; private set; } public RoutingStrategies RoutingStrategies { get; private set; } public IObservable> Raised => _raised; public IObservable RouteFinished => _routeFinished; public static RoutedEvent Register( string name, RoutingStrategies routingStrategy) where TEventArgs : RoutedEventArgs { Contract.Requires(name != null); return new RoutedEvent(name, routingStrategy, typeof(TOwner)); } public static RoutedEvent Register( string name, RoutingStrategies routingStrategy, Type ownerType) where TEventArgs : RoutedEventArgs { Contract.Requires(name != null); return new RoutedEvent(name, routingStrategy, ownerType); } public IDisposable AddClassHandler( Type targetType, EventHandler handler, RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble, bool handledEventsToo = false) { return Raised.Subscribe(args => { var sender = args.Item1; var e = args.Item2; if (targetType.GetTypeInfo().IsAssignableFrom(sender.GetType().GetTypeInfo()) && ((e.Route == RoutingStrategies.Direct) || (e.Route & routes) != 0) && (!e.Handled || handledEventsToo)) { try { handler.DynamicInvoke(sender, e); } catch (TargetInvocationException ex) { // Unwrap the inner exception. ExceptionDispatchInfo.Capture(ex.InnerException).Throw(); } } }); } internal void InvokeRaised(object sender, RoutedEventArgs e) { _raised.OnNext(Tuple.Create(sender, e)); } internal void InvokeRouteFinished(RoutedEventArgs e) { _routeFinished.OnNext(e); } } public class RoutedEvent : RoutedEvent where TEventArgs : RoutedEventArgs { public RoutedEvent(string name, RoutingStrategies routingStrategies, Type ownerType) : base(name, routingStrategies, typeof(TEventArgs), ownerType) { Contract.Requires(name != null); Contract.Requires(ownerType != null); } public IDisposable AddClassHandler( Func> handler, RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble, bool handledEventsToo = false) where TTarget : class, IInteractive { EventHandler adapter = (sender, e) => { var target = sender as TTarget; var args = e as TEventArgs; if (target != null && args != null) { handler(target)(args); } }; return AddClassHandler(typeof(TTarget), adapter, routes, handledEventsToo); } } }