|
|
@ -4,8 +4,6 @@ |
|
|
using System; |
|
|
using System; |
|
|
using System.Collections.Generic; |
|
|
using System.Collections.Generic; |
|
|
using System.Linq; |
|
|
using System.Linq; |
|
|
using System.Reactive.Disposables; |
|
|
|
|
|
using System.Reactive.Linq; |
|
|
|
|
|
using Avalonia.Layout; |
|
|
using Avalonia.Layout; |
|
|
using Avalonia.VisualTree; |
|
|
using Avalonia.VisualTree; |
|
|
|
|
|
|
|
|
@ -18,15 +16,14 @@ namespace Avalonia.Interactivity |
|
|
{ |
|
|
{ |
|
|
private Dictionary<RoutedEvent, List<EventSubscription>> _eventHandlers; |
|
|
private Dictionary<RoutedEvent, List<EventSubscription>> _eventHandlers; |
|
|
|
|
|
|
|
|
|
|
|
private static readonly Dictionary<Type, HandlerInvokeSignature> s_invokeHandlerCache = new Dictionary<Type, HandlerInvokeSignature>(); |
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
/// Gets the interactive parent of the object for bubbling and tunneling events.
|
|
|
/// Gets the interactive parent of the object for bubbling and tunneling events.
|
|
|
/// </summary>
|
|
|
/// </summary>
|
|
|
IInteractive IInteractive.InteractiveParent => ((IVisual)this).VisualParent as IInteractive; |
|
|
IInteractive IInteractive.InteractiveParent => ((IVisual)this).VisualParent as IInteractive; |
|
|
|
|
|
|
|
|
private Dictionary<RoutedEvent, List<EventSubscription>> EventHandlers |
|
|
private Dictionary<RoutedEvent, List<EventSubscription>> EventHandlers => _eventHandlers ?? (_eventHandlers = new Dictionary<RoutedEvent, List<EventSubscription>>()); |
|
|
{ |
|
|
|
|
|
get { return _eventHandlers ?? (_eventHandlers = new Dictionary<RoutedEvent, List<EventSubscription>>()); } |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
/// Adds a handler for the specified routed event.
|
|
|
/// Adds a handler for the specified routed event.
|
|
|
@ -45,24 +42,14 @@ namespace Avalonia.Interactivity |
|
|
Contract.Requires<ArgumentNullException>(routedEvent != null); |
|
|
Contract.Requires<ArgumentNullException>(routedEvent != null); |
|
|
Contract.Requires<ArgumentNullException>(handler != null); |
|
|
Contract.Requires<ArgumentNullException>(handler != null); |
|
|
|
|
|
|
|
|
List<EventSubscription> subscriptions; |
|
|
var subscription = new EventSubscription |
|
|
|
|
|
|
|
|
if (!EventHandlers.TryGetValue(routedEvent, out subscriptions)) |
|
|
|
|
|
{ |
|
|
|
|
|
subscriptions = new List<EventSubscription>(); |
|
|
|
|
|
EventHandlers.Add(routedEvent, subscriptions); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var sub = new EventSubscription |
|
|
|
|
|
{ |
|
|
{ |
|
|
Handler = handler, |
|
|
Handler = handler, |
|
|
Routes = routes, |
|
|
Routes = routes, |
|
|
AlsoIfHandled = handledEventsToo, |
|
|
AlsoIfHandled = handledEventsToo, |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
subscriptions.Add(sub); |
|
|
return AddEventSubscription(routedEvent, subscription); |
|
|
|
|
|
|
|
|
return Disposable.Create(() => subscriptions.Remove(sub)); |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
@ -80,7 +67,37 @@ namespace Avalonia.Interactivity |
|
|
RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble, |
|
|
RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble, |
|
|
bool handledEventsToo = false) where TEventArgs : RoutedEventArgs |
|
|
bool handledEventsToo = false) where TEventArgs : RoutedEventArgs |
|
|
{ |
|
|
{ |
|
|
return AddHandler(routedEvent, (Delegate)handler, routes, handledEventsToo); |
|
|
Contract.Requires<ArgumentNullException>(routedEvent != null); |
|
|
|
|
|
Contract.Requires<ArgumentNullException>(handler != null); |
|
|
|
|
|
|
|
|
|
|
|
// EventHandler delegate is not covariant, this forces us to create small wrapper
|
|
|
|
|
|
// that will cast our type erased instance and invoke it.
|
|
|
|
|
|
Type eventArgsType = routedEvent.EventArgsType; |
|
|
|
|
|
|
|
|
|
|
|
if (!s_invokeHandlerCache.TryGetValue(eventArgsType, out var invokeAdapter)) |
|
|
|
|
|
{ |
|
|
|
|
|
void InvokeAdapter(Delegate baseHandler, object sender, RoutedEventArgs args) |
|
|
|
|
|
{ |
|
|
|
|
|
var typedHandler = (EventHandler<TEventArgs>)baseHandler; |
|
|
|
|
|
var typedArgs = (TEventArgs)args; |
|
|
|
|
|
|
|
|
|
|
|
typedHandler(sender, typedArgs); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
invokeAdapter = InvokeAdapter; |
|
|
|
|
|
|
|
|
|
|
|
s_invokeHandlerCache.Add(eventArgsType, invokeAdapter); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var subscription = new EventSubscription |
|
|
|
|
|
{ |
|
|
|
|
|
InvokeAdapter = invokeAdapter, |
|
|
|
|
|
Handler = handler, |
|
|
|
|
|
Routes = routes, |
|
|
|
|
|
AlsoIfHandled = handledEventsToo, |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
return AddEventSubscription(routedEvent, subscription); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
/// <summary>
|
|
|
@ -196,10 +213,54 @@ namespace Avalonia.Interactivity |
|
|
|
|
|
|
|
|
if (correctRoute && notFinished) |
|
|
if (correctRoute && notFinished) |
|
|
{ |
|
|
{ |
|
|
sub.Handler.DynamicInvoke(this, e); |
|
|
if (sub.InvokeAdapter != null) |
|
|
|
|
|
{ |
|
|
|
|
|
sub.InvokeAdapter(sub.Handler, this, e); |
|
|
|
|
|
} |
|
|
|
|
|
else |
|
|
|
|
|
{ |
|
|
|
|
|
sub.Handler.DynamicInvoke(this, e); |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private List<EventSubscription> GetEventSubscriptions(RoutedEvent routedEvent) |
|
|
|
|
|
{ |
|
|
|
|
|
if (!EventHandlers.TryGetValue(routedEvent, out var subscriptions)) |
|
|
|
|
|
{ |
|
|
|
|
|
subscriptions = new List<EventSubscription>(); |
|
|
|
|
|
EventHandlers.Add(routedEvent, subscriptions); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return subscriptions; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private IDisposable AddEventSubscription(RoutedEvent routedEvent, EventSubscription subscription) |
|
|
|
|
|
{ |
|
|
|
|
|
List<EventSubscription> subscriptions = GetEventSubscriptions(routedEvent); |
|
|
|
|
|
|
|
|
|
|
|
subscriptions.Add(subscription); |
|
|
|
|
|
|
|
|
|
|
|
return new UnsubscribeDisposable(subscriptions, subscription); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private sealed class UnsubscribeDisposable : IDisposable |
|
|
|
|
|
{ |
|
|
|
|
|
private readonly List<EventSubscription> _subscriptions; |
|
|
|
|
|
private readonly EventSubscription _subscription; |
|
|
|
|
|
|
|
|
|
|
|
public UnsubscribeDisposable(List<EventSubscription> subscriptions, EventSubscription subscription) |
|
|
|
|
|
{ |
|
|
|
|
|
_subscriptions = subscriptions; |
|
|
|
|
|
_subscription = subscription; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public void Dispose() |
|
|
|
|
|
{ |
|
|
|
|
|
_subscriptions.Remove(_subscription); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|