Browse Source

Enable NRT in Avalonia.Interactivity.

pull/3621/head
Steven Kirk 7 years ago
parent
commit
b966bd390c
  1. 6
      src/Avalonia.Interactivity/Avalonia.Interactivity.csproj
  2. 20
      src/Avalonia.Interactivity/EventSubscription.cs
  3. 2
      src/Avalonia.Interactivity/IInteractive.cs
  4. 68
      src/Avalonia.Interactivity/Interactive.cs
  5. 5
      src/Avalonia.Interactivity/InteractiveExtensions.cs
  6. 18
      src/Avalonia.Interactivity/RoutedEvent.cs
  7. 8
      src/Avalonia.Interactivity/RoutedEventArgs.cs
  8. 6
      src/Avalonia.Interactivity/RoutedEventRegistry.cs

6
src/Avalonia.Interactivity/Avalonia.Interactivity.csproj

@ -1,6 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework> <TargetFramework>netstandard2.0</TargetFramework>
<Nullable>Enable</Nullable>
<WarningsAsErrors>CS8600;CS8602;CS8603</WarningsAsErrors>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Avalonia.Animation\Avalonia.Animation.csproj" /> <ProjectReference Include="..\Avalonia.Animation\Avalonia.Animation.csproj" />
@ -9,6 +11,4 @@
<ProjectReference Include="..\Avalonia.Visuals\Avalonia.Visuals.csproj" /> <ProjectReference Include="..\Avalonia.Visuals\Avalonia.Visuals.csproj" />
</ItemGroup> </ItemGroup>
<Import Project="..\..\build\Rx.props" /> <Import Project="..\..\build\Rx.props" />
</Project> </Project>

20
src/Avalonia.Interactivity/EventSubscription.cs

@ -9,12 +9,24 @@ namespace Avalonia.Interactivity
internal class EventSubscription internal class EventSubscription
{ {
public HandlerInvokeSignature InvokeAdapter { get; set; } public EventSubscription(
Delegate handler,
RoutingStrategies routes,
bool handledEventsToo,
HandlerInvokeSignature? invokeAdapter = null)
{
Handler = handler;
Routes = routes;
HandledEventsToo = handledEventsToo;
InvokeAdapter = invokeAdapter;
}
public Delegate Handler { get; set; } public HandlerInvokeSignature? InvokeAdapter { get; }
public RoutingStrategies Routes { get; set; } public Delegate Handler { get; }
public bool AlsoIfHandled { get; set; } public RoutingStrategies Routes { get; }
public bool HandledEventsToo { get; }
} }
} }

2
src/Avalonia.Interactivity/IInteractive.cs

@ -13,7 +13,7 @@ namespace Avalonia.Interactivity
/// <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 InteractiveParent { get; } IInteractive? InteractiveParent { get; }
/// <summary> /// <summary>
/// Adds a handler for the specified routed event. /// Adds a handler for the specified routed event.

68
src/Avalonia.Interactivity/Interactive.cs

@ -15,16 +15,16 @@ namespace Avalonia.Interactivity
/// </summary> /// </summary>
public class Interactive : Layoutable, IInteractive public class Interactive : Layoutable, IInteractive
{ {
private Dictionary<RoutedEvent, List<EventSubscription>> _eventHandlers; private Dictionary<RoutedEvent, List<EventSubscription>>? _eventHandlers;
private static readonly Dictionary<Type, HandlerInvokeSignature> s_invokeHandlerCache = new Dictionary<Type, HandlerInvokeSignature>(); 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 => _eventHandlers ?? (_eventHandlers = new Dictionary<RoutedEvent, List<EventSubscription>>()); private Dictionary<RoutedEvent, List<EventSubscription>> 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.
@ -40,16 +40,10 @@ namespace Avalonia.Interactivity
RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble, RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble,
bool handledEventsToo = false) bool handledEventsToo = false)
{ {
Contract.Requires<ArgumentNullException>(routedEvent != null); routedEvent = routedEvent ?? throw new ArgumentNullException(nameof(routedEvent));
Contract.Requires<ArgumentNullException>(handler != null); handler = handler ?? throw new ArgumentNullException(nameof(handler));
var subscription = new EventSubscription
{
Handler = handler,
Routes = routes,
AlsoIfHandled = handledEventsToo,
};
var subscription = new EventSubscription(handler, routes, handledEventsToo);
return AddEventSubscription(routedEvent, subscription); return AddEventSubscription(routedEvent, subscription);
} }
@ -68,12 +62,12 @@ 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
{ {
Contract.Requires<ArgumentNullException>(routedEvent != null); routedEvent = routedEvent ?? throw new ArgumentNullException(nameof(routedEvent));
Contract.Requires<ArgumentNullException>(handler != null); handler = handler ?? throw new ArgumentNullException(nameof(handler));
// EventHandler delegate is not covariant, this forces us to create small wrapper // EventHandler delegate is not covariant, this forces us to create small wrapper
// that will cast our type erased instance and invoke it. // that will cast our type erased instance and invoke it.
Type eventArgsType = routedEvent.EventArgsType; var eventArgsType = routedEvent.EventArgsType;
if (!s_invokeHandlerCache.TryGetValue(eventArgsType, out var invokeAdapter)) if (!s_invokeHandlerCache.TryGetValue(eventArgsType, out var invokeAdapter))
{ {
@ -90,14 +84,7 @@ namespace Avalonia.Interactivity
s_invokeHandlerCache.Add(eventArgsType, invokeAdapter); s_invokeHandlerCache.Add(eventArgsType, invokeAdapter);
} }
var subscription = new EventSubscription var subscription = new EventSubscription(handler, routes, handledEventsToo, invokeAdapter);
{
InvokeAdapter = invokeAdapter,
Handler = handler,
Routes = routes,
AlsoIfHandled = handledEventsToo,
};
return AddEventSubscription(routedEvent, subscription); return AddEventSubscription(routedEvent, subscription);
} }
@ -108,12 +95,11 @@ namespace Avalonia.Interactivity
/// <param name="handler">The handler.</param> /// <param name="handler">The handler.</param>
public void RemoveHandler(RoutedEvent routedEvent, Delegate handler) public void RemoveHandler(RoutedEvent routedEvent, Delegate handler)
{ {
Contract.Requires<ArgumentNullException>(routedEvent != null); routedEvent = routedEvent ?? throw new ArgumentNullException(nameof(routedEvent));
Contract.Requires<ArgumentNullException>(handler != null); handler = handler ?? throw new ArgumentNullException(nameof(handler));
List<EventSubscription> subscriptions = null;
if (_eventHandlers?.TryGetValue(routedEvent, out subscriptions) == true) if (_eventHandlers is object &&
_eventHandlers.TryGetValue(routedEvent, out var subscriptions) == true)
{ {
subscriptions.RemoveAll(x => x.Handler == handler); subscriptions.RemoveAll(x => x.Handler == handler);
} }
@ -137,9 +123,14 @@ namespace Avalonia.Interactivity
/// <param name="e">The event args.</param> /// <param name="e">The event args.</param>
public void RaiseEvent(RoutedEventArgs e) public void RaiseEvent(RoutedEventArgs e)
{ {
Contract.Requires<ArgumentNullException>(e != null); e = e ?? throw new ArgumentNullException(nameof(e));
e.Source = e.Source ?? this; if (e.RoutedEvent == null)
{
throw new ArgumentException("Cannot raise an event whose RoutedEvent is null.");
}
e.Source ??= this;
if (e.RoutedEvent.RoutingStrategies == RoutingStrategies.Direct) if (e.RoutedEvent.RoutingStrategies == RoutingStrategies.Direct)
{ {
@ -167,7 +158,7 @@ namespace Avalonia.Interactivity
/// <param name="e">The event args.</param> /// <param name="e">The event args.</param>
private void BubbleEvent(RoutedEventArgs e) private void BubbleEvent(RoutedEventArgs e)
{ {
Contract.Requires<ArgumentNullException>(e != null); e = e ?? throw new ArgumentNullException(nameof(e));
e.Route = RoutingStrategies.Bubble; e.Route = RoutingStrategies.Bubble;
@ -182,7 +173,7 @@ namespace Avalonia.Interactivity
/// <param name="e">The event args.</param> /// <param name="e">The event args.</param>
private void TunnelEvent(RoutedEventArgs e) private void TunnelEvent(RoutedEventArgs e)
{ {
Contract.Requires<ArgumentNullException>(e != null); e = e ?? throw new ArgumentNullException(nameof(e));
e.Route = RoutingStrategies.Tunnel; e.Route = RoutingStrategies.Tunnel;
@ -197,18 +188,17 @@ namespace Avalonia.Interactivity
/// <param name="e">The event args.</param> /// <param name="e">The event args.</param>
private void RaiseEventImpl(RoutedEventArgs e) private void RaiseEventImpl(RoutedEventArgs e)
{ {
Contract.Requires<ArgumentNullException>(e != null); e = e ?? throw new ArgumentNullException(nameof(e));
e.RoutedEvent.InvokeRaised(this, e);
List<EventSubscription> subscriptions = null; e.RoutedEvent!.InvokeRaised(this, e);
if (_eventHandlers?.TryGetValue(e.RoutedEvent, out subscriptions) == true) if (_eventHandlers is object &&
_eventHandlers.TryGetValue(e.RoutedEvent, out var subscriptions) == true)
{ {
foreach (var sub in subscriptions.ToList()) foreach (var sub in subscriptions.ToList())
{ {
bool correctRoute = (e.Route & sub.Routes) != 0; bool correctRoute = (e.Route & sub.Routes) != 0;
bool notFinished = !e.Handled || sub.AlsoIfHandled; bool notFinished = !e.Handled || sub.HandledEventsToo;
if (correctRoute && notFinished) if (correctRoute && notFinished)
{ {
@ -313,7 +303,7 @@ namespace Avalonia.Interactivity
{ {
_preTraverse.Execute(target, _args); _preTraverse.Execute(target, _args);
IInteractive parent = target.InteractiveParent; var parent = target.InteractiveParent;
if (parent != null) if (parent != null)
{ {

5
src/Avalonia.Interactivity/InteractiveExtensions.cs

@ -2,8 +2,6 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq; using System.Reactive.Linq;
namespace Avalonia.Interactivity namespace Avalonia.Interactivity
@ -30,6 +28,9 @@ namespace Avalonia.Interactivity
bool handledEventsToo = false) bool handledEventsToo = false)
where TEventArgs : RoutedEventArgs where TEventArgs : RoutedEventArgs
{ {
o = o ?? throw new ArgumentNullException(nameof(o));
routedEvent = routedEvent ?? throw new ArgumentNullException(nameof(routedEvent));
return Observable.Create<TEventArgs>(x => o.AddHandler( return Observable.Create<TEventArgs>(x => o.AddHandler(
routedEvent, routedEvent,
(_, e) => x.OnNext(e), (_, e) => x.OnNext(e),

18
src/Avalonia.Interactivity/RoutedEvent.cs

@ -25,10 +25,14 @@ namespace Avalonia.Interactivity
Type eventArgsType, Type eventArgsType,
Type ownerType) Type ownerType)
{ {
Contract.Requires<ArgumentNullException>(name != null); name = name ?? throw new ArgumentNullException(nameof(name));
Contract.Requires<ArgumentNullException>(eventArgsType != null); eventArgsType = eventArgsType ?? throw new ArgumentNullException(nameof(name));
Contract.Requires<ArgumentNullException>(ownerType != null); ownerType = ownerType ?? throw new ArgumentNullException(nameof(name));
Contract.Requires<InvalidCastException>(typeof(RoutedEventArgs).IsAssignableFrom(eventArgsType));
if (!typeof(RoutedEventArgs).IsAssignableFrom(eventArgsType))
{
throw new InvalidCastException("eventArgsType must be derived from RoutedEventArgs.");
}
EventArgsType = eventArgsType; EventArgsType = eventArgsType;
Name = name; Name = name;
@ -52,7 +56,7 @@ namespace Avalonia.Interactivity
RoutingStrategies routingStrategy) RoutingStrategies routingStrategy)
where TEventArgs : RoutedEventArgs where TEventArgs : RoutedEventArgs
{ {
Contract.Requires<ArgumentNullException>(name != null); name = name ?? throw new ArgumentNullException(nameof(name));
var routedEvent = new RoutedEvent<TEventArgs>(name, routingStrategy, typeof(TOwner)); var routedEvent = new RoutedEvent<TEventArgs>(name, routingStrategy, typeof(TOwner));
RoutedEventRegistry.Instance.Register(typeof(TOwner), routedEvent); RoutedEventRegistry.Instance.Register(typeof(TOwner), routedEvent);
@ -65,7 +69,7 @@ namespace Avalonia.Interactivity
Type ownerType) Type ownerType)
where TEventArgs : RoutedEventArgs where TEventArgs : RoutedEventArgs
{ {
Contract.Requires<ArgumentNullException>(name != null); name = name ?? throw new ArgumentNullException(nameof(name));
var routedEvent = new RoutedEvent<TEventArgs>(name, routingStrategy, ownerType); var routedEvent = new RoutedEvent<TEventArgs>(name, routingStrategy, ownerType);
RoutedEventRegistry.Instance.Register(ownerType, routedEvent); RoutedEventRegistry.Instance.Register(ownerType, routedEvent);
@ -108,8 +112,6 @@ namespace Avalonia.Interactivity
public RoutedEvent(string name, RoutingStrategies routingStrategies, Type ownerType) public RoutedEvent(string name, RoutingStrategies routingStrategies, Type ownerType)
: base(name, routingStrategies, typeof(TEventArgs), ownerType) : base(name, routingStrategies, typeof(TEventArgs), ownerType)
{ {
Contract.Requires<ArgumentNullException>(name != null);
Contract.Requires<ArgumentNullException>(ownerType != null);
} }
[Obsolete("Use overload taking Action<TTarget, TEventArgs>.")] [Obsolete("Use overload taking Action<TTarget, TEventArgs>.")]

8
src/Avalonia.Interactivity/RoutedEventArgs.cs

@ -11,12 +11,12 @@ namespace Avalonia.Interactivity
{ {
} }
public RoutedEventArgs(RoutedEvent routedEvent) public RoutedEventArgs(RoutedEvent? routedEvent)
{ {
RoutedEvent = routedEvent; RoutedEvent = routedEvent;
} }
public RoutedEventArgs(RoutedEvent routedEvent, IInteractive source) public RoutedEventArgs(RoutedEvent? routedEvent, IInteractive? source)
{ {
RoutedEvent = routedEvent; RoutedEvent = routedEvent;
Source = source; Source = source;
@ -24,10 +24,10 @@ namespace Avalonia.Interactivity
public bool Handled { get; set; } public bool Handled { get; set; }
public RoutedEvent RoutedEvent { get; set; } public RoutedEvent? RoutedEvent { get; set; }
public RoutingStrategies Route { get; set; } public RoutingStrategies Route { get; set; }
public IInteractive Source { get; set; } public IInteractive? Source { get; set; }
} }
} }

6
src/Avalonia.Interactivity/RoutedEventRegistry.cs

@ -32,8 +32,8 @@ namespace Avalonia.Interactivity
/// </remarks> /// </remarks>
public void Register(Type type, RoutedEvent @event) public void Register(Type type, RoutedEvent @event)
{ {
Contract.Requires<ArgumentNullException>(type != null); type = type ?? throw new ArgumentNullException(nameof(type));
Contract.Requires<ArgumentNullException>(@event != null); @event = @event ?? throw new ArgumentNullException(nameof(@event));
if (!_registeredRoutedEvents.TryGetValue(type, out var list)) if (!_registeredRoutedEvents.TryGetValue(type, out var list))
{ {
@ -66,7 +66,7 @@ namespace Avalonia.Interactivity
/// <returns>All routed events registered with the provided type.</returns> /// <returns>All routed events registered with the provided type.</returns>
public IReadOnlyList<RoutedEvent> GetRegistered(Type type) public IReadOnlyList<RoutedEvent> GetRegistered(Type type)
{ {
Contract.Requires<ArgumentNullException>(type != null); type = type ?? throw new ArgumentNullException(nameof(type));
if (_registeredRoutedEvents.TryGetValue(type, out var events)) if (_registeredRoutedEvents.TryGetValue(type, out var events))
{ {

Loading…
Cancel
Save