Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(NativeAnimated): Adds support for animated events #874

Merged
merged 1 commit into from
Nov 10, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@
<Compile Include="$(MSBuildThisFileDirectory)UIManager\Dimensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UIManager\Events\Event.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UIManager\Events\EventDispatcher.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UIManager\Events\IEventDispatcherListener.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UIManager\Events\RCTEventEmitter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UIManager\Events\TouchEventType.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UIManager\Events\TouchEventTypeExtensions.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ public class EventDispatcher : ILifecycleEventListener

private readonly IDictionary<long, int> _eventCookieToLastEventIndex = new Dictionary<long, int>();
private readonly IDictionary<string, short> _eventNameToEventId = new Dictionary<string, short>();
private readonly List<Event> _eventStaging = new List<Event>();
private readonly IList<Event> _eventStaging = new List<Event>();
private readonly IList<IEventDispatcherListener> _listeners = new List<IEventDispatcherListener>();

private readonly ReactContext _reactContext;

Expand Down Expand Up @@ -115,12 +116,45 @@ public void DispatchEvent(Event @event)
if (@event == null)
throw new ArgumentNullException(nameof(@event));

var eventHandled = false;
foreach (var listener in _listeners)
{
if (listener.OnEventDispatch(@event))
{
eventHandled = true;
}
}

// If the event was handled by one of the event listeners, don't send it to JavaScript.
if (eventHandled)
{
return;
}

lock (_eventsStagingLock)
{
_eventStaging.Add(@event);
}
}

/// <summary>
/// Adds a listener to this <see cref="EventDispatcher"/>.
/// </summary>
/// <param name="listener">The listener.</param>
public void AddListener(IEventDispatcherListener listener)
{
_listeners.Add(listener);
}

/// <summary>
/// Removes a listener from this <see cref="EventDispatcher"/>.
/// </summary>
/// <param name="listener">The listener.</param>
public void RemoveListener(IEventDispatcherListener listener)
{
_listeners.Remove(listener);
}

/// <summary>
/// Called when the host receives the resume event.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
namespace ReactNative.UIManager.Events
{
/// <summary>
/// Interface used to intercept events dispatched by <see cref="EventDispatcher"/>.
/// </summary>
public interface IEventDispatcherListener
{
/// <summary>
/// Called on every time an event is dispatched using <see cref="EventDispatcher.DispatchEvent(Event)"/>.
/// Will be called from the same thread that the event is being
/// dispatched from.
/// </summary>
/// <param name="event">Event that was dispatched.</param>
/// <returns>
/// If the event was handled. If true the event won't be sent to
/// JavaScript.
/// </returns>
bool OnEventDispatch(Event @event);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ namespace ReactNative.UIManager.Events
/// <summary>
/// JavaScript event emitter.
/// </summary>
public sealed class RCTEventEmitter : JavaScriptModuleBase
public class RCTEventEmitter : JavaScriptModuleBase
{
/// <summary>
/// Receive an event.
/// </summary>
/// <param name="targetTag">The target tag.</param>
/// <param name="eventName">The event name.</param>
/// <param name="event">The event data.</param>
public void receiveEvent(int targetTag, string eventName, JObject @event)
public virtual void receiveEvent(int targetTag, string eventName, JObject @event)
{
Invoke(targetTag, eventName, @event);
}
Expand All @@ -25,7 +25,7 @@ public void receiveEvent(int targetTag, string eventName, JObject @event)
/// <param name="eventName">The event name.</param>
/// <param name="touches">The touches.</param>
/// <param name="changedIndexes">The changed indices.</param>
public void receiveTouches(string eventName, JArray touches, JArray changedIndexes)
public virtual void receiveTouches(string eventName, JArray touches, JArray changedIndexes)
{
Invoke(eventName, touches, changedIndexes);
}
Expand Down
47 changes: 47 additions & 0 deletions ReactWindows/ReactNative/Animated/EventAnimationDriver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using Newtonsoft.Json.Linq;
using ReactNative.UIManager.Events;
using System;
using System.Collections.Generic;
using static System.FormattableString;

namespace ReactNative.Animated
{
class EventAnimationDriver : RCTEventEmitter
{
private readonly IList<string> _eventPath;

public EventAnimationDriver(IList<string> eventPath, ValueAnimatedNode valueNode)
{
_eventPath = eventPath;
ValueNode = valueNode;
}

public ValueAnimatedNode ValueNode
{
get;
}

public override void receiveEvent(int targetTag, string eventName, JObject @event)
{
if (@event == null)
{
throw new ArgumentNullException(nameof(@event), "Native animated events must have event data.");
}

// Get the new value for the node by looking into the event map using the provided event path.
var current = @event;
for (var i = 0; i < _eventPath.Count - 1; i++)
{
current = (JObject)current[_eventPath[i]];
}

ValueNode.Value = current.Value<double>(_eventPath[_eventPath.Count - 1]);
}

public override void receiveTouches(string eventName, JArray touches, JArray changedIndexes)
{
throw new NotSupportedException(
Invariant($"Method '{nameof(receiveTouches)}' is not support by native animated events."));
}
}
}
31 changes: 28 additions & 3 deletions ReactWindows/ReactNative/Animated/NativeAnimatedModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,8 @@ public override string Name
public override void Initialize()
{
var ctx = Context;
var uiImpl = ctx.GetNativeModule<UIManagerModule>().UIImplementation;
var nodesManager = new NativeAnimatedNodesManager(uiImpl);
var uiManager = ctx.GetNativeModule<UIManagerModule>();
var nodesManager = new NativeAnimatedNodesManager(uiManager);
_animatedFrameCallback = (sender, args) =>
{
try
Expand Down Expand Up @@ -308,7 +308,7 @@ public void disconnectAnimatedNodes(int parentNodeTag, int childNodeTag)
/// Connects animated node to view.
/// </summary>
/// <param name="animatedNodeTag">Animated node tag.</param>
/// <param name="viewTag">React view tag.s</param>
/// <param name="viewTag">React view tag.</param>
[ReactMethod]
public void connectAnimatedNodeToView(int animatedNodeTag, int viewTag)
{
Expand All @@ -327,5 +327,30 @@ public void disconnectAnimatedNodeFromView(int animatedNodeTag, int viewTag)
_operations.Add(manager =>
manager.DisconnectAnimatedNodeFromView(animatedNodeTag, viewTag));
}

/// <summary>
/// Adds an animated event to view.
/// </summary>
/// <param name="viewTag">The view tag.</param>
/// <param name="eventName">The event name.</param>
/// <param name="eventMapping">The event mapping.</param>
[ReactMethod]
public void addAnimatedEventToView(int viewTag, string eventName, JObject eventMapping)
{
_operations.Add(manager =>
manager.AddAnimatedEventToView(viewTag, eventName, eventMapping));
}

/// <summary>
/// Removes an animated event from view.
/// </summary>
/// <param name="viewTag">The view tag.</param>
/// <param name="eventName">The event name.</param>
[ReactMethod]
public void removeAnimatedEventFromView(int viewTag, string eventName)
{
_operations.Add(manager =>
manager.RemoveAnimatedEventFromView(viewTag, eventName));
}
}
}
105 changes: 99 additions & 6 deletions ReactWindows/ReactNative/Animated/NativeAnimatedNodesManager.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Newtonsoft.Json.Linq;
using ReactNative.Bridge;
using ReactNative.UIManager;
using ReactNative.UIManager.Events;
using System;
using System.Collections.Generic;
using static System.FormattableString;
Expand All @@ -25,18 +26,22 @@ namespace ReactNative.Animated
/// <remarks>
/// This class should be accessed only from the dispatcher thread.
/// </remarks>
class NativeAnimatedNodesManager
class NativeAnimatedNodesManager : IEventDispatcherListener
{
private readonly Dictionary<int, AnimatedNode> _animatedNodes = new Dictionary<int, AnimatedNode>();
private readonly List<AnimationDriver> _activeAnimations = new List<AnimationDriver>();
private readonly List<AnimatedNode> _updatedNodes = new List<AnimatedNode>();
private readonly IDictionary<int, AnimatedNode> _animatedNodes = new Dictionary<int, AnimatedNode>();
private readonly IList<AnimationDriver> _activeAnimations = new List<AnimationDriver>();
private readonly IList<AnimatedNode> _updatedNodes = new List<AnimatedNode>();
private readonly IDictionary<Tuple<int, string>, EventAnimationDriver> _eventDrivers = new Dictionary<Tuple<int, string>, EventAnimationDriver>();
private readonly IReadOnlyDictionary<string, object> _customEventTypes;
private readonly UIImplementation _uiImplementation;

private int _animatedGraphBFSColor = 0;

public NativeAnimatedNodesManager(UIImplementation uiImplementation)
public NativeAnimatedNodesManager(UIManagerModule uiManager)
{
this._uiImplementation = uiImplementation;
_uiImplementation = uiManager.UIImplementation;
uiManager.EventDispatcher.AddListener(this);
Copy link
Collaborator Author

@rozele rozele Nov 10, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EventDispatcher [](start = 22, length = 15)

We may want to consider making this disposable / removable. #WontFix

_customEventTypes = GetEventTypes(uiManager);
}

public bool HasActiveAnimations
Expand Down Expand Up @@ -259,6 +264,62 @@ public void DisconnectAnimatedNodeFromView(int animatedNodeTag, int viewTag)
propsAnimatedNode.ConnectedViewTag = -1;
}

public void AddAnimatedEventToView(int viewTag, string eventName, JObject eventMapping)
{
var nodeTag = eventMapping.Value<int>("animatedValueTag");
var node = default(AnimatedNode);
if (!_animatedNodes.TryGetValue(nodeTag, out node))
{
throw new InvalidOperationException(
Invariant($"Animated node with tag '{nodeTag}' does not exist."));
}

var valueNode = node as ValueAnimatedNode;
if (valueNode == null)
{
throw new InvalidOperationException(
Invariant($"Animated node connected to event should be of type '{nameof(ValueAnimatedNode)}'."));
}

var pathList = eventMapping["nativeEventPath"].ToObject<string[]>();
var @event = new EventAnimationDriver(pathList, valueNode);
_eventDrivers.Add(Tuple.Create(viewTag, eventName), @event);
}

public void RemoveAnimatedEventFromView(int viewTag, string eventName)
{
_eventDrivers.Remove(Tuple.Create(viewTag, eventName));
}

public bool OnEventDispatch(Event @event)
{
// Only support events dispatched from the dispatcher thread.
if (!DispatcherHelpers.IsOnDispatcher())
{
return false;
}

if (_eventDrivers.Count > 0)
{
var eventName = @event.EventName;
var customEventName = default(string);
if (TryGetRegistrationName(eventName, out customEventName))
{
eventName = customEventName;
}

var eventDriver = default(EventAnimationDriver);
if (_eventDrivers.TryGetValue(Tuple.Create(@event.ViewTag, eventName), out eventDriver))
{
@event.Dispatch(eventDriver);
_updatedNodes.Add(eventDriver.ValueNode);
return true;
}
}

return false;
}

/// <summary>
/// Animation loop performs BFS over the graph of animated nodes.
/// </summary>
Expand Down Expand Up @@ -461,5 +522,37 @@ private AnimatedNode GetNode(int tag)

return node;
}

private bool TryGetRegistrationName(string eventName, out string customEventName)
{
var customEvent = default(object);
if (!_customEventTypes.TryGetValue(eventName, out customEvent))
{
customEventName = default(string);
return false;
}

var customEventMap = customEvent as IReadOnlyDictionary<string, object>;
if (customEventMap == null)
{
customEventName = default(string);
return false;
}

var customEventRegistrationName = default(object);
if (!customEventMap.TryGetValue("registrationName", out customEventRegistrationName))
{
customEventName = default(string);
return false;
}

customEventName = customEventRegistrationName as string;
return customEventName != null;
}

private static IReadOnlyDictionary<string, object> GetEventTypes(UIManagerModule uiManager)
{
return (IReadOnlyDictionary<string, object>)uiManager.Constants["customDirectEventTypes"];
}
}
}
1 change: 1 addition & 0 deletions ReactWindows/ReactNative/ReactNative.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@
<Compile Include="Animated\AdditionAnimatedNode.cs" />
<Compile Include="Animated\DiffClampAnimatedNode.cs" />
<Compile Include="Animated\DivisionAnimatedNode.cs" />
<Compile Include="Animated\EventAnimationDriver.cs" />
<Compile Include="Animated\MultiplicationAnimatedNode.cs" />
<Compile Include="Animated\NativeAnimatedModule.cs" />
<Compile Include="Animated\NativeAnimatedNodesManager.cs" />
Expand Down