A cross-platform UI framework for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

200 lines
6.7 KiB

using System;
using Avalonia.Collections.Pooled;
namespace Avalonia.Interactivity
{
/// <summary>
/// Holds the route for a routed event and supports raising an event on that route.
/// </summary>
public class EventRoute : IDisposable
{
private readonly RoutedEvent _event;
private PooledList<RouteItem>? _route;
/// <summary>
/// Initializes a new instance of the <see cref="RoutedEvent"/> class.
/// </summary>
/// <param name="e">The routed event to be raised.</param>
public EventRoute(RoutedEvent e)
{
e = e ?? throw new ArgumentNullException(nameof(e));
_event = e;
_route = null;
}
/// <summary>
/// Gets a value indicating whether the route has any handlers.
/// </summary>
public bool HasHandlers => _route?.Count > 0;
/// <summary>
/// Adds a handler to the route.
/// </summary>
/// <param name="target">The target on which the event should be raised.</param>
/// <param name="handler">The handler for the event.</param>
/// <param name="routes">The routing strategies to listen to.</param>
/// <param name="handledEventsToo">
/// If true the handler will be raised even when the routed event is marked as handled.
/// </param>
/// <param name="adapter">
/// An optional adapter which if supplied, will be called with <paramref name="handler"/>
/// and the parameters for the event. This adapter can be used to avoid calling
/// `DynamicInvoke` on the handler.
/// </param>
public void Add(
IInteractive target,
Delegate handler,
RoutingStrategies routes,
bool handledEventsToo = false,
Action<Delegate, object, RoutedEventArgs>? adapter = null)
{
target = target ?? throw new ArgumentNullException(nameof(target));
handler = handler ?? throw new ArgumentNullException(nameof(handler));
_route ??= new PooledList<RouteItem>(16);
_route.Add(new RouteItem(target, handler, adapter, routes, handledEventsToo));
}
/// <summary>
/// Adds a class handler to the route.
/// </summary>
/// <param name="target">The target on which the event should be raised.</param>
public void AddClassHandler(IInteractive target)
{
target = target ?? throw new ArgumentNullException(nameof(target));
_route ??= new PooledList<RouteItem>(16);
_route.Add(new RouteItem(target, null, null, 0, false));
}
/// <summary>
/// Raises an event along the route.
/// </summary>
/// <param name="source">The event source.</param>
/// <param name="e">The event args.</param>
public void RaiseEvent(IInteractive source, RoutedEventArgs e)
{
source = source ?? throw new ArgumentNullException(nameof(source));
e = e ?? throw new ArgumentNullException(nameof(e));
e.Source = source;
if (_event.RoutingStrategies == RoutingStrategies.Direct)
{
e.Route = RoutingStrategies.Direct;
RaiseEventImpl(e);
_event.InvokeRouteFinished(e);
}
else
{
if (_event.RoutingStrategies.HasFlagCustom(RoutingStrategies.Tunnel))
{
e.Route = RoutingStrategies.Tunnel;
RaiseEventImpl(e);
_event.InvokeRouteFinished(e);
}
if (_event.RoutingStrategies.HasFlagCustom(RoutingStrategies.Bubble))
{
e.Route = RoutingStrategies.Bubble;
RaiseEventImpl(e);
_event.InvokeRouteFinished(e);
}
}
}
/// <summary>
/// Disposes of the event route.
/// </summary>
public void Dispose()
{
_route?.Dispose();
_route = null;
}
private void RaiseEventImpl(RoutedEventArgs e)
{
if (_route is null)
{
return;
}
if (e.Source is null)
{
throw new ArgumentException("Event source may not be null", nameof(e));
}
IInteractive? lastTarget = null;
var start = 0;
var end = _route.Count;
var step = 1;
if (e.Route == RoutingStrategies.Tunnel)
{
start = end - 1;
step = end = -1;
}
for (var i = start; i != end; i += step)
{
var entry = _route[i];
// If we've got to a new control then call any RoutedEvent.Raised listeners.
if (entry.Target != lastTarget)
{
if (!e.Handled)
{
_event.InvokeRaised(entry.Target, e);
}
// If this is a direct event and we've already raised events then we're finished.
if (e.Route == RoutingStrategies.Direct && lastTarget is object)
{
return;
}
lastTarget = entry.Target;
}
// Raise the event handler.
if (entry.Handler is object &&
entry.Routes.HasFlagCustom(e.Route) &&
(!e.Handled || entry.HandledEventsToo))
{
if (entry.Adapter is object)
{
entry.Adapter(entry.Handler, entry.Target, e);
}
else
{
entry.Handler.DynamicInvoke(entry.Target, e);
}
}
}
}
private readonly struct RouteItem
{
public RouteItem(
IInteractive target,
Delegate? handler,
Action<Delegate, object, RoutedEventArgs>? adapter,
RoutingStrategies routes,
bool handledEventsToo)
{
Target = target;
Handler = handler;
Adapter = adapter;
Routes = routes;
HandledEventsToo = handledEventsToo;
}
public IInteractive Target { get; }
public Delegate? Handler { get; }
public Action<Delegate, object, RoutedEventArgs>? Adapter { get; }
public RoutingStrategies Routes { get; }
public bool HandledEventsToo { get; }
}
}
}