Browse Source

Merge branch 'master' into fix-styledproperty-defaultvalue-box

pull/2935/head
Dariusz Komosiński 7 years ago
committed by GitHub
parent
commit
c3c52ee637
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 17
      src/Avalonia.Animation/Animatable.cs
  2. 16
      src/Avalonia.Base/PriorityBindingEntry.cs
  3. 4
      src/Avalonia.Interactivity/EventSubscription.cs
  4. 101
      src/Avalonia.Interactivity/Interactive.cs

17
src/Avalonia.Animation/Animatable.cs

@ -45,16 +45,17 @@ namespace Avalonia.Animation
{ {
get get
{ {
if (_transitions == null) if (_transitions is null)
_transitions = new Transitions(); _transitions = new Transitions();
if (_previousTransitions == null) if (_previousTransitions is null)
_previousTransitions = new Dictionary<AvaloniaProperty, IDisposable>(); _previousTransitions = new Dictionary<AvaloniaProperty, IDisposable>();
return _transitions; return _transitions;
} }
set set
{ {
SetAndRaise(TransitionsProperty, ref _transitions, value); SetAndRaise(TransitionsProperty, ref _transitions, value);
} }
} }
@ -66,18 +67,20 @@ namespace Avalonia.Animation
/// <param name="e">The event args.</param> /// <param name="e">The event args.</param>
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e) protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e)
{ {
if (e.Priority != BindingPriority.Animation && Transitions != null && _previousTransitions != null) if (_transitions is null || _previousTransitions is null || e.Priority == BindingPriority.Animation) return;
{
var match = Transitions.FirstOrDefault(x => x.Property == e.Property);
if (match != null) // PERF-SENSITIVE: Called on every property change. Don't use LINQ here (too many allocations).
foreach (var transition in Transitions)
{
if (transition.Property == e.Property)
{ {
if (_previousTransitions.TryGetValue(e.Property, out var dispose)) if (_previousTransitions.TryGetValue(e.Property, out var dispose))
dispose.Dispose(); dispose.Dispose();
var instance = match.Apply(this, Clock ?? Avalonia.Animation.Clock.GlobalClock, e.OldValue, e.NewValue); var instance = transition.Apply(this, Clock ?? Avalonia.Animation.Clock.GlobalClock, e.OldValue, e.NewValue);
_previousTransitions[e.Property] = instance; _previousTransitions[e.Property] = instance;
return;
} }
} }
} }

16
src/Avalonia.Base/PriorityBindingEntry.cs

@ -2,6 +2,7 @@
// 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.Runtime.ExceptionServices;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Threading; using Avalonia.Threading;
@ -10,9 +11,9 @@ namespace Avalonia
/// <summary> /// <summary>
/// A registered binding in a <see cref="PriorityValue"/>. /// A registered binding in a <see cref="PriorityValue"/>.
/// </summary> /// </summary>
internal class PriorityBindingEntry : IDisposable internal class PriorityBindingEntry : IDisposable, IObserver<object>
{ {
private PriorityLevel _owner; private readonly PriorityLevel _owner;
private IDisposable _subscription; private IDisposable _subscription;
/// <summary> /// <summary>
@ -85,7 +86,7 @@ namespace Avalonia
Description = ((IDescription)binding).Description; Description = ((IDescription)binding).Description;
} }
_subscription = binding.Subscribe(ValueChanged, Completed); _subscription = binding.Subscribe(this);
} }
/// <summary> /// <summary>
@ -96,7 +97,7 @@ namespace Avalonia
_subscription?.Dispose(); _subscription?.Dispose();
} }
private void ValueChanged(object value) void IObserver<object>.OnNext(object value)
{ {
void Signal() void Signal()
{ {
@ -132,7 +133,7 @@ namespace Avalonia
} }
} }
private void Completed() void IObserver<object>.OnCompleted()
{ {
HasCompleted = true; HasCompleted = true;
@ -145,5 +146,10 @@ namespace Avalonia
Dispatcher.UIThread.Post(() => _owner.Completed(this)); Dispatcher.UIThread.Post(() => _owner.Completed(this));
} }
} }
void IObserver<object>.OnError(Exception error)
{
ExceptionDispatchInfo.Capture(error).Throw();
}
} }
} }

4
src/Avalonia.Interactivity/EventSubscription.cs

@ -5,8 +5,12 @@ using System;
namespace Avalonia.Interactivity namespace Avalonia.Interactivity
{ {
internal delegate void HandlerInvokeSignature(Delegate baseHandler, object sender, RoutedEventArgs args);
internal class EventSubscription internal class EventSubscription
{ {
public HandlerInvokeSignature InvokeAdapter { get; set; }
public Delegate Handler { get; set; } public Delegate Handler { get; set; }
public RoutingStrategies Routes { get; set; } public RoutingStrategies Routes { get; set; }

101
src/Avalonia.Interactivity/Interactive.cs

@ -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);
}
}
} }
} }

Loading…
Cancel
Save