diff --git a/ReactWindows/ReactNative.Tests/ReactNative.Tests.csproj b/ReactWindows/ReactNative.Tests/ReactNative.Tests.csproj
index a43b5ee7db9..231ee65064b 100644
--- a/ReactWindows/ReactNative.Tests/ReactNative.Tests.csproj
+++ b/ReactWindows/ReactNative.Tests/ReactNative.Tests.csproj
@@ -125,7 +125,9 @@
+
+
diff --git a/ReactWindows/ReactNative.Tests/UIManager/FrameworkElementExtensionsTests.cs b/ReactWindows/ReactNative.Tests/UIManager/FrameworkElementExtensionsTests.cs
new file mode 100644
index 00000000000..b26c5984b51
--- /dev/null
+++ b/ReactWindows/ReactNative.Tests/UIManager/FrameworkElementExtensionsTests.cs
@@ -0,0 +1,57 @@
+using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
+using ReactNative.Bridge;
+using ReactNative.UIManager;
+using System;
+using Windows.UI.Xaml.Controls;
+
+namespace ReactNative.Tests.UIManager
+{
+ [TestClass]
+ public class FrameworkElementExtensionsTests
+ {
+ [Microsoft.VisualStudio.TestPlatform.UnitTestFramework.AppContainer.UITestMethod]
+ public void FrameworkElementExtensions_ArgumentChecks()
+ {
+ var element = new Button();
+
+ AssertEx.Throws(
+ () => FrameworkElementExtensions.SetTag(null, 0),
+ ex => Assert.AreEqual("view", ex.ParamName));
+
+ AssertEx.Throws(
+ () => FrameworkElementExtensions.SetReactContext(null, null),
+ ex => Assert.AreEqual("view", ex.ParamName));
+
+ AssertEx.Throws(
+ () => FrameworkElementExtensions.GetTag(null),
+ ex => Assert.AreEqual("view", ex.ParamName));
+
+ AssertEx.Throws(
+ () => FrameworkElementExtensions.GetReactContext(null),
+ ex => Assert.AreEqual("view", ex.ParamName));
+ }
+
+ [Microsoft.VisualStudio.TestPlatform.UnitTestFramework.AppContainer.UITestMethod]
+ public void FrameworkElementExtensions_ExistingTag()
+ {
+ var button = new Button();
+ button.Tag = new object();
+
+ AssertEx.Throws(() => button.SetTag(1));
+ AssertEx.Throws(() => button.SetReactContext(null));
+ }
+
+ [Microsoft.VisualStudio.TestPlatform.UnitTestFramework.AppContainer.UITestMethod]
+ public void FrameworkElementExtensions_Get_Set()
+ {
+ var button = new Button();
+
+ button.SetTag(42);
+ Assert.AreEqual(42, button.GetTag());
+
+ button.SetReactContext(null);
+ Assert.IsNull(button.GetReactContext());
+ }
+
+ }
+}
diff --git a/ReactWindows/ReactNative.Tests/UIManager/PropertySetterTests.cs b/ReactWindows/ReactNative.Tests/UIManager/PropertySetterTests.cs
index 96ec8514ddb..758fad270ba 100644
--- a/ReactWindows/ReactNative.Tests/UIManager/PropertySetterTests.cs
+++ b/ReactWindows/ReactNative.Tests/UIManager/PropertySetterTests.cs
@@ -350,6 +350,21 @@ public ReactShadowNode CreateShadowNodeInstance()
throw new NotImplementedException();
}
+ public FrameworkElement CreateView(ThemedReactContext themedContext, JavaScriptResponderHandler jsResponderHandler)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void OnDropViewInstance(ThemedReactContext themedReactContext, FrameworkElement view)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void ReceiveCommand(FrameworkElement view, int commandId, JArray args)
+ {
+ throw new NotImplementedException();
+ }
+
public void UpdateProperties(FrameworkElement viewToUpdate, CatalystStylesDiffMap properties)
{
throw new NotImplementedException();
diff --git a/ReactWindows/ReactNative.Tests/UIManager/RootViewHelperTests.cs b/ReactWindows/ReactNative.Tests/UIManager/RootViewHelperTests.cs
new file mode 100644
index 00000000000..0a06609d87d
--- /dev/null
+++ b/ReactWindows/ReactNative.Tests/UIManager/RootViewHelperTests.cs
@@ -0,0 +1,24 @@
+using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
+using ReactNative.UIManager;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+
+namespace ReactNative.Tests.UIManager
+{
+ [TestClass]
+ public class RootViewHelperTests
+ {
+ [TestMethod]
+ public void RootViewHelper_Null()
+ {
+ Assert.IsNull(RootViewHelper.GetRootView(null));
+ }
+
+ class TestRootView : Panel, IRootView
+ {
+ public void OnChildStartedNativeGesture(RoutedEventArgs ev)
+ {
+ }
+ }
+ }
+}
diff --git a/ReactWindows/ReactNative.Tests/UIManager/UIManagerModuleTests.cs b/ReactWindows/ReactNative.Tests/UIManager/UIManagerModuleTests.cs
index 0d11ffe7d5d..16f4fe17851 100644
--- a/ReactWindows/ReactNative.Tests/UIManager/UIManagerModuleTests.cs
+++ b/ReactWindows/ReactNative.Tests/UIManager/UIManagerModuleTests.cs
@@ -1,4 +1,5 @@
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
+using Newtonsoft.Json.Linq;
using ReactNative.Bridge;
using ReactNative.Tests.Constants;
using ReactNative.UIManager;
@@ -132,6 +133,21 @@ public ReactShadowNode CreateShadowNodeInstance()
return null;
}
+ public FrameworkElement CreateView(ThemedReactContext themedContext, JavaScriptResponderHandler jsResponderHandler)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void OnDropViewInstance(ThemedReactContext themedReactContext, FrameworkElement view)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void ReceiveCommand(FrameworkElement view, int commandId, JArray args)
+ {
+ throw new NotImplementedException();
+ }
+
public void UpdateExtraData(FrameworkElement viewToUpdate, object extraData)
{
throw new NotImplementedException();
diff --git a/ReactWindows/ReactNative.Tests/UIManager/ViewAtIndexTests.cs b/ReactWindows/ReactNative.Tests/UIManager/ViewAtIndexTests.cs
index 23bb7d67db4..b642ac6ba8d 100644
--- a/ReactWindows/ReactNative.Tests/UIManager/ViewAtIndexTests.cs
+++ b/ReactWindows/ReactNative.Tests/UIManager/ViewAtIndexTests.cs
@@ -12,9 +12,9 @@ public void ViewAtIndex_Comparator()
var v1 = new ViewAtIndex(17, 6);
var v2 = new ViewAtIndex(42, 17);
- Assert.IsTrue(ViewAtIndex.Comparer.Compare(v1, v2) < 0);
- Assert.IsTrue(ViewAtIndex.Comparer.Compare(v2, v1) > 0);
- Assert.AreEqual(0, ViewAtIndex.Comparer.Compare(v1, v1));
+ Assert.IsTrue(ViewAtIndex.IndexComparer.Compare(v1, v2) < 0);
+ Assert.IsTrue(ViewAtIndex.IndexComparer.Compare(v2, v1) > 0);
+ Assert.AreEqual(0, ViewAtIndex.IndexComparer.Compare(v1, v1));
}
[TestMethod]
diff --git a/ReactWindows/ReactNative.Tests/UIManager/ViewManagerRegistryTests.cs b/ReactWindows/ReactNative.Tests/UIManager/ViewManagerRegistryTests.cs
index 82047426b84..f36596f8d58 100644
--- a/ReactWindows/ReactNative.Tests/UIManager/ViewManagerRegistryTests.cs
+++ b/ReactWindows/ReactNative.Tests/UIManager/ViewManagerRegistryTests.cs
@@ -1,4 +1,5 @@
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
+using Newtonsoft.Json.Linq;
using ReactNative.UIManager;
using System;
using System.Collections.Generic;
@@ -90,6 +91,21 @@ public ReactShadowNode CreateShadowNodeInstance()
throw new NotImplementedException();
}
+ public FrameworkElement CreateView(ThemedReactContext themedContext, JavaScriptResponderHandler jsResponderHandler)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void OnDropViewInstance(ThemedReactContext themedReactContext, FrameworkElement view)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void ReceiveCommand(FrameworkElement view, int commandId, JArray args)
+ {
+ throw new NotImplementedException();
+ }
+
public void UpdateExtraData(FrameworkElement viewToUpdate, object extraData)
{
throw new NotImplementedException();
diff --git a/ReactWindows/ReactNative.Tests/UIManager/ViewManagersPropertyCacheTests.cs b/ReactWindows/ReactNative.Tests/UIManager/ViewManagersPropertyCacheTests.cs
index 88b14748884..cb18801d3e9 100644
--- a/ReactWindows/ReactNative.Tests/UIManager/ViewManagersPropertyCacheTests.cs
+++ b/ReactWindows/ReactNative.Tests/UIManager/ViewManagersPropertyCacheTests.cs
@@ -190,7 +190,9 @@ public void ViewManagersPropertyCache_Defaults()
class EmptyTest : IViewManager
{
- public IReadOnlyDictionary CommandsMap
+ #region IViewManager
+
+ public string Name
{
get
{
@@ -198,7 +200,7 @@ public IReadOnlyDictionary CommandsMap
}
}
- public IReadOnlyDictionary ExportedCustomBubblingEventTypeConstants
+ public IReadOnlyDictionary CommandsMap
{
get
{
@@ -206,7 +208,7 @@ public IReadOnlyDictionary ExportedCustomBubblingEventTypeConsta
}
}
- public IReadOnlyDictionary ExportedCustomDirectEventTypeConstants
+ public IReadOnlyDictionary ExportedCustomBubblingEventTypeConstants
{
get
{
@@ -214,7 +216,7 @@ public IReadOnlyDictionary ExportedCustomDirectEventTypeConstant
}
}
- public IReadOnlyDictionary ExportedViewConstants
+ public IReadOnlyDictionary ExportedCustomDirectEventTypeConstants
{
get
{
@@ -222,7 +224,7 @@ public IReadOnlyDictionary ExportedViewConstants
}
}
- public string Name
+ public IReadOnlyDictionary ExportedViewConstants
{
get
{
@@ -243,7 +245,17 @@ public ReactShadowNode CreateShadowNodeInstance()
throw new NotImplementedException();
}
- public void UpdateExtraData(FrameworkElement viewToUpdate, object extraData)
+ public FrameworkElement CreateView(ThemedReactContext themedContext, JavaScriptResponderHandler jsResponderHandler)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void OnDropViewInstance(ThemedReactContext themedReactContext, FrameworkElement view)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void ReceiveCommand(FrameworkElement view, int commandId, JArray args)
{
throw new NotImplementedException();
}
@@ -252,6 +264,13 @@ public void UpdateProperties(FrameworkElement viewToUpdate, CatalystStylesDiffMa
{
throw new NotImplementedException();
}
+
+ public void UpdateExtraData(FrameworkElement viewToUpdate, object extraData)
+ {
+ throw new NotImplementedException();
+ }
+
+ #endregion
}
class ViewManagerValueTest : IViewManager
@@ -272,7 +291,7 @@ public void Bar(FrameworkElement element, int index, string value)
BarValues[index] = value;
}
- #region IViewManager Implementation
+ #region IViewManager
public string Name
{
@@ -327,6 +346,21 @@ public ReactShadowNode CreateShadowNodeInstance()
throw new NotImplementedException();
}
+ public FrameworkElement CreateView(ThemedReactContext themedContext, JavaScriptResponderHandler jsResponderHandler)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void OnDropViewInstance(ThemedReactContext themedReactContext, FrameworkElement view)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void ReceiveCommand(FrameworkElement view, int commandId, JArray args)
+ {
+ throw new NotImplementedException();
+ }
+
public void UpdateProperties(FrameworkElement viewToUpdate, CatalystStylesDiffMap properties)
{
throw new NotImplementedException();
@@ -540,6 +574,21 @@ public ReactShadowNode CreateShadowNodeInstance()
throw new NotImplementedException();
}
+ public FrameworkElement CreateView(ThemedReactContext themedContext, JavaScriptResponderHandler jsResponderHandler)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void OnDropViewInstance(ThemedReactContext themedReactContext, FrameworkElement view)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void ReceiveCommand(FrameworkElement view, int commandId, JArray args)
+ {
+ throw new NotImplementedException();
+ }
+
public void UpdateProperties(FrameworkElement viewToUpdate, CatalystStylesDiffMap properties)
{
throw new NotImplementedException();
diff --git a/ReactWindows/ReactNative/IReactInstanceManager.cs b/ReactWindows/ReactNative/IReactInstanceManager.cs
index a1852ffdd4f..f005bd435fb 100644
--- a/ReactWindows/ReactNative/IReactInstanceManager.cs
+++ b/ReactWindows/ReactNative/IReactInstanceManager.cs
@@ -47,8 +47,6 @@ public interface IReactInstanceManager : IDisposable
//public abstract void onResume(DefaultHardwareBackBtnHandler defaultBackButtonImpl);
- void Dispose();
-
///
/// Attach given {@param rootView} to a catalyst instance manager and start JS application
///
diff --git a/ReactWindows/ReactNative/ReactNative.csproj b/ReactWindows/ReactNative/ReactNative.csproj
index b5ba7f63f96..b5b76a2783a 100644
--- a/ReactWindows/ReactNative/ReactNative.csproj
+++ b/ReactWindows/ReactNative/ReactNative.csproj
@@ -191,6 +191,7 @@
+
@@ -207,6 +208,9 @@
+
+
+
diff --git a/ReactWindows/ReactNative/UIManager/Animation/AnimationRegistry.cs b/ReactWindows/ReactNative/UIManager/Animation/AnimationRegistry.cs
new file mode 100644
index 00000000000..4dd83a4f4b5
--- /dev/null
+++ b/ReactWindows/ReactNative/UIManager/Animation/AnimationRegistry.cs
@@ -0,0 +1,9 @@
+namespace ReactNative.UIManager.Animation
+{
+ public class AnimationRegistry
+ {
+ public AnimationRegistry()
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/ReactWindows/ReactNative/UIManager/FrameworkElementExtensions.cs b/ReactWindows/ReactNative/UIManager/FrameworkElementExtensions.cs
new file mode 100644
index 00000000000..43e7f2931ce
--- /dev/null
+++ b/ReactWindows/ReactNative/UIManager/FrameworkElementExtensions.cs
@@ -0,0 +1,91 @@
+using System;
+using Windows.UI.Xaml;
+
+namespace ReactNative.UIManager
+{
+ static class FrameworkElementExtensions
+ {
+ public static void SetTag(this FrameworkElement view, int tag)
+ {
+ if (view == null)
+ throw new ArgumentNullException(nameof(view));
+
+ var existingData = view.Tag;
+ var elementData = default(FrameworkElementData);
+ if (existingData == null)
+ {
+ elementData = new FrameworkElementData();
+ view.Tag = elementData;
+ }
+ else
+ {
+ elementData = existingData as FrameworkElementData;
+ if (elementData == null)
+ {
+ throw new InvalidOperationException("Tag for FrameworkElement has already been set.");
+ }
+ }
+
+ elementData.Tag = tag;
+ }
+
+ public static int GetTag(this FrameworkElement view)
+ {
+ if (view == null)
+ throw new ArgumentNullException(nameof(view));
+
+ var elementData = view.Tag as FrameworkElementData;
+ if (elementData == null || elementData.Tag == null)
+ {
+ throw new InvalidOperationException("Could not get tag for view.");
+ }
+
+ return elementData.Tag.Value;
+ }
+
+ public static void SetReactContext(this FrameworkElement view, ThemedReactContext context)
+ {
+ if (view == null)
+ throw new ArgumentNullException(nameof(view));
+
+ var existingData = view.Tag;
+ var elementData = default(FrameworkElementData);
+ if (existingData == null)
+ {
+ elementData = new FrameworkElementData();
+ view.Tag = elementData;
+ }
+ else
+ {
+ elementData = existingData as FrameworkElementData;
+ if (elementData == null)
+ {
+ throw new InvalidOperationException("Tag for FrameworkElement has already been set.");
+ }
+ }
+
+ elementData.Context = context;
+ }
+
+ public static ThemedReactContext GetReactContext(this FrameworkElement view)
+ {
+ if (view == null)
+ throw new ArgumentNullException(nameof(view));
+
+ var elementData = view.Tag as FrameworkElementData;
+ if (elementData == null)
+ {
+ throw new InvalidOperationException("Could not get context for view.");
+ }
+
+ return elementData.Context;
+ }
+
+ class FrameworkElementData
+ {
+ public ThemedReactContext Context { get; set; }
+
+ public int? Tag { get; set; }
+ }
+ }
+}
diff --git a/ReactWindows/ReactNative/UIManager/IViewManager.cs b/ReactWindows/ReactNative/UIManager/IViewManager.cs
index 3862c207c67..34dd9d0e040 100644
--- a/ReactWindows/ReactNative/UIManager/IViewManager.cs
+++ b/ReactWindows/ReactNative/UIManager/IViewManager.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using Newtonsoft.Json.Linq;
+using System.Collections.Generic;
using Windows.UI.Xaml;
namespace ReactNative.UIManager
@@ -44,6 +45,34 @@ public interface IViewManager
/// The shadow node instance.
ReactShadowNode CreateShadowNodeInstance();
+ ///
+ /// Creates a view and installs event emitters on it.
+ ///
+ /// The context.
+ /// The responder handler.
+ /// The view.
+ FrameworkElement CreateView(ThemedReactContext themedContext, JavaScriptResponderHandler jsResponderHandler);
+
+ ///
+ /// Called when view is detached from view hierarchy and allows for
+ /// additional cleanup by the
+ /// subclass.
+ ///
+ /// The react context.
+ /// The view.
+ void OnDropViewInstance(ThemedReactContext themedReactContext, FrameworkElement view);
+
+ ///
+ /// Implement this method to receive events/commands directly from
+ /// JavaScript through the .
+ ///
+ ///
+ /// The view instance that should receive the command.
+ ///
+ /// Identifer for the command.
+ /// Optional arguments for the command.
+ void ReceiveCommand(FrameworkElement view, int commandId, JArray args);
+
///
/// Update the properties of the given view.
///
diff --git a/ReactWindows/ReactNative/UIManager/JavaScriptResponderHandler.cs b/ReactWindows/ReactNative/UIManager/JavaScriptResponderHandler.cs
index df1a574911e..7ebb44534b8 100644
--- a/ReactWindows/ReactNative/UIManager/JavaScriptResponderHandler.cs
+++ b/ReactWindows/ReactNative/UIManager/JavaScriptResponderHandler.cs
@@ -1,6 +1,17 @@
-namespace ReactNative.UIManager
+using System;
+
+namespace ReactNative.UIManager
{
public class JavaScriptResponderHandler
{
+ internal void ClearJavaScriptResponder()
+ {
+ throw new NotImplementedException();
+ }
+
+ internal void SetJavaScriptResponder(int initialReactTag, object p)
+ {
+ throw new NotImplementedException();
+ }
}
}
\ No newline at end of file
diff --git a/ReactWindows/ReactNative/UIManager/NativeViewHierarchyManager.cs b/ReactWindows/ReactNative/UIManager/NativeViewHierarchyManager.cs
index 0d1dc402133..453037b88e7 100644
--- a/ReactWindows/ReactNative/UIManager/NativeViewHierarchyManager.cs
+++ b/ReactWindows/ReactNative/UIManager/NativeViewHierarchyManager.cs
@@ -1,10 +1,13 @@
using Newtonsoft.Json.Linq;
using ReactNative.Bridge;
using ReactNative.Tracing;
+using ReactNative.UIManager.Animation;
using System;
using System.Collections.Generic;
using System.Globalization;
+using Windows.UI.Popups;
using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
namespace ReactNative.UIManager
{
@@ -37,6 +40,9 @@ namespace ReactNative.UIManager
///
/// TODO:
/// 1) AnimationRegistry
+ /// 2) UpdateLayout
+ /// 3) Measure
+ /// 4) ShowPopupMenu
///
public class NativeViewHierarchyManager
{
@@ -46,7 +52,12 @@ public class NativeViewHierarchyManager
private readonly ViewManagerRegistry _viewManagers;
private readonly JavaScriptResponderHandler _jsResponderHandler;
private readonly RootViewManager _rootViewManager;
+ private readonly AnimationRegistry _animationRegistry;
+ ///
+ /// Instantiates the .
+ ///
+ /// The view manager registry.
public NativeViewHierarchyManager(ViewManagerRegistry viewManagers)
{
_viewManagers = viewManagers;
@@ -55,9 +66,35 @@ public NativeViewHierarchyManager(ViewManagerRegistry viewManagers)
_rootTags = new Dictionary();
_jsResponderHandler = new JavaScriptResponderHandler();
_rootViewManager = new RootViewManager();
+ _animationRegistry = new AnimationRegistry();
}
- public void UpdateProperties(int tag, string className, CatalystStylesDiffMap properties)
+ ///
+ /// The animation registry.
+ ///
+ public AnimationRegistry Animations
+ {
+ get
+ {
+ return _animationRegistry;
+ }
+ }
+
+ ///
+ /// Signals if layout animation is enabled.
+ ///
+ public bool LayoutAnimationEnabled
+ {
+ private get;
+ set;
+ }
+
+ ///
+ /// Updates the properties of the view with the given tag.
+ ///
+ /// The view tag.
+ /// The properties.
+ public void UpdateProperties(int tag, CatalystStylesDiffMap properties)
{
DispatcherHelpers.AssertOnDispatcher();
var viewManager = ResolveViewManager(tag);
@@ -65,14 +102,28 @@ public void UpdateProperties(int tag, string className, CatalystStylesDiffMap pr
viewManager.UpdateProperties(viewToUpdate, properties);
}
- public void UpdateViewExtraData(int tag, object data)
+ ///
+ /// Updates the extra data for the view with the given tag.
+ ///
+ /// The view tag.
+ /// The extra data.
+ public void UpdateViewExtraData(int tag, object extraData)
{
DispatcherHelpers.AssertOnDispatcher();
var viewManager = ResolveViewManager(tag);
var viewToUpdate = ResolveView(tag);
- viewManager.UpdateExtraData(viewToUpdate, data);
+ viewManager.UpdateExtraData(viewToUpdate, extraData);
}
+ ///
+ /// Updates the layout of a view.
+ ///
+ /// The parent view tag.
+ /// The view tag.
+ /// The left coordinate.
+ /// The right coordinate.
+ /// The layout width.
+ /// The layout height.
public void UpdateLayout(int parentTag, int tag, int x, int y, int width, int height)
{
DispatcherHelpers.AssertOnDispatcher();
@@ -82,83 +133,384 @@ public void UpdateLayout(int parentTag, int tag, int x, int y, int width, int he
{
var viewToUpdate = ResolveView(tag);
// TODO: call viewToUpdate.Measure()
+ throw new NotImplementedException();
+ }
+ }
+
+ ///
+ /// Creates a view with the given tag and class name.
+ ///
+ /// The context.
+ /// The tag.
+ /// The class name.
+ /// The properties.
+ public void CreateView(ThemedReactContext themedContext, int tag, string className, CatalystStylesDiffMap initialProperties)
+ {
+ DispatcherHelpers.AssertOnDispatcher();
+ using (Tracer.Trace(Tracer.TRACE_TAG_REACT_VIEW, "NativeViewHierarcyManager.CreateView")
+ .With("tag", tag)
+ .With("className", className))
+ {
+ var viewManager = _viewManagers.Get(className);
+ var view = viewManager.CreateView(themedContext, _jsResponderHandler);
+ _tagsToViews.Add(tag, view);
+ _tagsToViewManagers.Add(tag, viewManager);
+ // Uses an extension method and a conditional weak table to
+ // store the tag of the view. The conditional weak table does
+ // not prevent the view from being garbage collected.
+ view.SetTag(tag);
+
+ if (initialProperties != null)
+ {
+ viewManager.UpdateProperties(view, initialProperties);
+ }
}
}
- private FrameworkElement ResolveView(int tag)
+ ///
+ /// Manages the children of a react view.
+ ///
+ /// The tag of the view to manager.
+ /// Child indices to remove.
+ /// Views to add.
+ /// Tags to delete.
+ public void ManageChildren(int tag, int[] indicesToRemove, ViewAtIndex[] viewsToAdd, int[] tagsToDelete)
{
- var view = default(FrameworkElement);
- if (!_tagsToViews.TryGetValue(tag, out view))
+ var viewManager = default(IViewManager);
+ if (!_tagsToViewManagers.TryGetValue(tag, out viewManager))
{
throw new InvalidOperationException(
string.Format(
CultureInfo.InvariantCulture,
- "Trying to resolve view with tag '{0}' which doesn't exist.",
+ "Trying to manage children with tag '{0}' which doesn't exist.",
tag));
}
- return view;
+ var viewGroupManager = (ViewGroupManager)viewManager;
+ var viewToManage = (Panel)_tagsToViews[tag];
+
+ var lastIndexToRemove = viewGroupManager.GetChildCount(viewToManage);
+ if (indicesToRemove != null)
+ {
+ for (var i = indicesToRemove.Length - 1; i >= 0; --i)
+ {
+ var indexToRemove = indicesToRemove[i];
+ if (indexToRemove < 0)
+ {
+ throw new InvalidOperationException(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "Trying to remove a negative index '{0}' on view tag '{1}'.",
+ indexToRemove,
+ tag));
+ }
+
+ if (indexToRemove >= viewGroupManager.GetChildCount(viewToManage))
+ {
+ throw new InvalidOperationException(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "Trying to remove a view index '{0}' greater than the child could for view tag '{1}'.",
+ indexToRemove,
+ tag));
+ }
+
+ if (indexToRemove >= lastIndexToRemove)
+ {
+ throw new InvalidOperationException(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "Trying to remove an out of order index '{0}' (last index was '{1}') for view tag '{2}'.",
+ indexToRemove,
+ lastIndexToRemove,
+ tag));
+ }
+
+ viewGroupManager.RemoveChildAt(viewToManage, indexToRemove);
+ lastIndexToRemove = indexToRemove;
+ }
+ }
+
+ if (viewsToAdd != null)
+ {
+ for (var i = 0; i < viewsToAdd.Length; ++i)
+ {
+ var viewAtIndex = viewsToAdd[i];
+ var viewToAdd = default(FrameworkElement);
+ if (!_tagsToViews.TryGetValue(viewAtIndex.Tag, out viewToAdd))
+ {
+ throw new InvalidOperationException(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "Trying to add unknown view tag '{0}'.",
+ viewAtIndex.Tag));
+ }
+
+ viewGroupManager.AddView(viewToManage, viewToAdd, viewAtIndex.Index);
+ }
+ }
+
+ if (tagsToDelete != null)
+ {
+ for (var i = 0; i < tagsToDelete.Length; ++i)
+ {
+ var tagToDelete = tagsToDelete[i];
+ var viewToDestroy = default(FrameworkElement);
+ if (!_tagsToViews.TryGetValue(tagToDelete, out viewToDestroy))
+ {
+ throw new InvalidOperationException(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "Trying to destroy unknown view tag '{0}'.",
+ tagToDelete));
+ }
+
+ DropView(viewToDestroy);
+ }
+ }
}
- private IViewManager ResolveViewManager(int tag)
+ ///
+ /// Remove the root view with the given tag.
+ ///
+ /// The root view tag.
+ public void RemoveRootView(int rootViewTag)
{
- var viewManager = default(IViewManager);
- if (!_tagsToViewManagers.TryGetValue(tag, out viewManager))
+ DispatcherHelpers.AssertOnDispatcher();
+ if (!_rootTags.ContainsKey(rootViewTag))
{
throw new InvalidOperationException(
string.Format(
CultureInfo.InvariantCulture,
- "ViewManager for tag '{0}' could not be found.",
- tag));
+ "View with tag '{0}' is not registered as a root view.",
+ rootViewTag));
}
- return viewManager;
+ var rootView = _tagsToViews[rootViewTag];
+ DropView(rootView);
+ _rootTags.Remove(rootViewTag);
}
- internal void RemoveRootView(int rootViewTag)
+ ///
+ /// Measures a view and sets the output buffer to (x, y, width, height).
+ ///
+ /// The view tag.
+ /// The output buffer.
+ public void Measure(int tag, int[] outputBuffer)
{
+ DispatcherHelpers.AssertOnDispatcher();
+ var v = default(FrameworkElement);
+ if (!_tagsToViews.TryGetValue(tag, out v))
+ {
+ throw new ArgumentOutOfRangeException(nameof(tag));
+ }
+
+ var rootView = (FrameworkElement)RootViewHelper.GetRootView(v);
+ if (rootView == null)
+ {
+ throw new InvalidOperationException(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "Native view '{0}' is no longer on screen.",
+ tag));
+ }
+
+ //TODO: implement get position, etc.
throw new NotImplementedException();
}
- internal void CreateView(ThemedReactContext themedContext, int tag, string className, CatalystStylesDiffMap initialProps)
+ ///
+ /// Adds a root view with the given tag.
+ ///
+ /// The tag.
+ /// The root view.
+ /// The themed context.
+ public void AddRootView(int tag, SizeMonitoringFrameLayout view, ThemedReactContext themedContext)
{
- throw new NotImplementedException();
+ AddRootViewGroup(tag, view, themedContext);
}
- internal void ManageChildren(int tag, int[] indicesToRemove, ViewAtIndex[] viewsToAdd, int[] tagsToDelete)
+ ///
+ /// Find the view target for touch coordinates.
+ ///
+ /// The view tag.
+ /// The x-coordinate of the touch event.
+ /// The y-coordinate of the touch event.
+ /// The view target.
+ public int FindTargetForTouch(int reactTag, double touchX, double touchY)
{
- throw new NotImplementedException();
+ var view = default(FrameworkElement);
+ if (!_tagsToViews.TryGetValue(reactTag, out view))
+ {
+ throw new InvalidOperationException(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "Could not find view with tag '{0}'.",
+ reactTag));
+ }
+
+ return TouchTargetHelper.FindTargetTagForTouch(touchX, touchY, (Panel)view);
}
- internal void AddRootView(int tag, SizeMonitoringFrameLayout rootView, ThemedReactContext themedRootContext)
+ ///
+ /// Sets the JavaScript responder handler for a view.
+ ///
+ /// The view tag.
+ /// The initial tag.
+ ///
+ /// Flag to block the native responder.
+ ///
+ public void SetJavaScriptResponder(int reactTag, int initialReactTag, bool blockNativeResponder)
{
+ if (!blockNativeResponder)
+ {
+ _jsResponderHandler.SetJavaScriptResponder(initialReactTag, null);
+ return;
+ }
+
throw new NotImplementedException();
}
- internal void SetJavaScriptResponder(int tag, int initialTag, bool blockNativeResponder)
+ ///
+ /// Clears the JavaScript responder.
+ ///
+ public void ClearJavaScriptResponder()
{
- throw new NotImplementedException();
+ _jsResponderHandler.ClearJavaScriptResponder();
}
- internal void Measure(int reactTag, int[] _measureBuffer)
+ ///
+ /// Dispatches a command to a view.
+ ///
+ /// The view tag.
+ /// The command identifier.
+ /// The command arguments.
+ public void DispatchCommand(int reactTag, int commandId, JArray args)
{
- throw new NotImplementedException();
+ DispatcherHelpers.AssertOnDispatcher();
+ var view = default(FrameworkElement);
+ if (!_tagsToViews.TryGetValue(reactTag, out view))
+ {
+ throw new InvalidOperationException(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "Trying to send command to a non-existent view with tag '{0}.",
+ reactTag));
+ }
+
+ var viewManager = ResolveViewManager(reactTag);
+ viewManager.ReceiveCommand(view, commandId, args);
}
- internal void ClearJavaScriptResponder()
+ ///
+ /// Shows a .
+ ///
+ ///
+ /// The tag of the anchor view (the is
+ /// displayed next to this view); this needs to be the tag of a native
+ /// view (shadow views cannot be anchors).
+ ///
+ /// The menu items as an array of strings.
+ ///
+ /// A callback used with the position of the selected item as the first
+ /// argument, or no arguments if the menu is dismissed.
+ ///
+ public void ShowPopupMenu(int tag, string[] items, ICallback success)
{
+ DispatcherHelpers.AssertOnDispatcher();
+ var view = ResolveView(tag);
+
+ var menu = new PopupMenu();
+ for (var i = 0; i < items.Length; ++i)
+ {
+ menu.Commands.Add(new UICommand(
+ items[i],
+ cmd =>
+ {
+ success.Invoke(cmd.Id);
+ },
+ i));
+ }
+
+ // TODO: figure out where to popup the menu
+ // TODO: add continuation that calls the callback with empty args
throw new NotImplementedException();
}
- internal void DispatchCommand(int reactTag, int commandId, JArray commandArgs)
+ private FrameworkElement ResolveView(int tag)
{
- throw new NotImplementedException();
+ var view = default(FrameworkElement);
+ if (!_tagsToViews.TryGetValue(tag, out view))
+ {
+ throw new InvalidOperationException(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "Trying to resolve view with tag '{0}' which doesn't exist.",
+ tag));
+ }
+
+ return view;
}
- internal void ShowPopupMenu(object tag, JArray items, ICallback success)
+ private IViewManager ResolveViewManager(int tag)
{
- throw new NotImplementedException();
+ var viewManager = default(IViewManager);
+ if (!_tagsToViewManagers.TryGetValue(tag, out viewManager))
+ {
+ throw new InvalidOperationException(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "ViewManager for tag '{0}' could not be found.",
+ tag));
+ }
+
+ return viewManager;
+ }
+
+ private void AddRootViewGroup(int tag, Panel view, ThemedReactContext themedContext)
+ {
+ DispatcherHelpers.AssertOnDispatcher();
+ _tagsToViews.Add(tag, view);
+ _tagsToViewManagers.Add(tag, _rootViewManager);
+ _rootTags.Add(tag, true);
+ view.SetTag(tag);
+ }
+
+ private void DropView(FrameworkElement view)
+ {
+ DispatcherHelpers.AssertOnDispatcher();
+ var tag = view.GetTag();
+ if (!_rootTags.ContainsKey(tag))
+ {
+ // For non-root views, we notify the view manager with `OnDropViewInstance`
+ var mgr = ResolveViewManager(tag);
+ mgr.OnDropViewInstance(view.GetReactContext(), view);
+ }
+
+ var viewManager = default(IViewManager);
+ if (_tagsToViewManagers.TryGetValue(tag, out viewManager))
+ {
+ var viewGroup = view as Panel;
+ var viewGroupManager = viewManager as ViewGroupManager;
+ if (viewGroup != null && viewGroupManager != null)
+ {
+ for (var i = viewGroupManager.GetChildCount(viewGroup) - 1; i >= 0; --i)
+ {
+ var child = viewGroupManager.GetChildAt(viewGroup, i);
+ var managedChild = default(FrameworkElement);
+ if (_tagsToViews.TryGetValue(child.GetTag(), out managedChild))
+ {
+ DropView(managedChild);
+ }
+ }
+ }
+
+ viewGroupManager.RemoveAllChildren(viewGroup);
+ }
+
+ _tagsToViews.Remove(tag);
+ _tagsToViewManagers.Remove(tag);
}
}
}
diff --git a/ReactWindows/ReactNative/UIManager/RootViewHelper.cs b/ReactWindows/ReactNative/UIManager/RootViewHelper.cs
new file mode 100644
index 00000000000..e4f6b391ea8
--- /dev/null
+++ b/ReactWindows/ReactNative/UIManager/RootViewHelper.cs
@@ -0,0 +1,35 @@
+using Windows.UI.Xaml;
+
+namespace ReactNative.UIManager
+{
+ ///
+ /// Helper methods for root view management.
+ ///
+ public static class RootViewHelper
+ {
+ ///
+ /// Returns the root view of a givenview in a react application.
+ ///
+ /// The view instance.
+ /// The root view instance.
+ public static IRootView GetRootView(FrameworkElement view)
+ {
+ var current = view;
+ while (true)
+ {
+ if (current == null)
+ {
+ return null;
+ }
+
+ var rootView = current as IRootView;
+ if (rootView != null)
+ {
+ return rootView;
+ }
+
+ current = (FrameworkElement)current.Parent;
+ }
+ }
+ }
+}
diff --git a/ReactWindows/ReactNative/UIManager/RootViewManager.cs b/ReactWindows/ReactNative/UIManager/RootViewManager.cs
index b4d3d56d064..b06c0de7e45 100644
--- a/ReactWindows/ReactNative/UIManager/RootViewManager.cs
+++ b/ReactWindows/ReactNative/UIManager/RootViewManager.cs
@@ -1,6 +1,88 @@
-namespace ReactNative.UIManager
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+using Windows.UI.Xaml;
+
+namespace ReactNative.UIManager
{
- internal class RootViewManager
+ internal class RootViewManager : IViewManager
{
+ public IReadOnlyDictionary CommandsMap
+ {
+ get
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ public IReadOnlyDictionary ExportedCustomBubblingEventTypeConstants
+ {
+ get
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ public IReadOnlyDictionary ExportedCustomDirectEventTypeConstants
+ {
+ get
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ public IReadOnlyDictionary ExportedViewConstants
+ {
+ get
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ public string Name
+ {
+ get
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ public IReadOnlyDictionary NativeProperties
+ {
+ get
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ public ReactShadowNode CreateShadowNodeInstance()
+ {
+ throw new NotImplementedException();
+ }
+
+ public FrameworkElement CreateView(ThemedReactContext themedContext, JavaScriptResponderHandler jsResponderHandler)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void OnDropViewInstance(ThemedReactContext themedReactContext, FrameworkElement view)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void ReceiveCommand(FrameworkElement view, int commandId, JArray args)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void UpdateExtraData(FrameworkElement viewToUpdate, object extraData)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void UpdateProperties(FrameworkElement viewToUpdate, CatalystStylesDiffMap properties)
+ {
+ throw new NotImplementedException();
+ }
}
}
\ No newline at end of file
diff --git a/ReactWindows/ReactNative/UIManager/SizeMonitoringFrameLayout.cs b/ReactWindows/ReactNative/UIManager/SizeMonitoringFrameLayout.cs
index 2cfb13e99d3..47932d91761 100644
--- a/ReactWindows/ReactNative/UIManager/SizeMonitoringFrameLayout.cs
+++ b/ReactWindows/ReactNative/UIManager/SizeMonitoringFrameLayout.cs
@@ -1,11 +1,12 @@
using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
namespace ReactNative.UIManager
{
///
/// allows registering for size change events. The main purpose for this class is to hide complexity of ReactRootView
///
- public class SizeMonitoringFrameLayout
+ public class SizeMonitoringFrameLayout : Panel
{
public interface OnSizeChangedListener
{
diff --git a/ReactWindows/ReactNative/UIManager/TouchTargetHelper.cs b/ReactWindows/ReactNative/UIManager/TouchTargetHelper.cs
new file mode 100644
index 00000000000..b7c09f530ac
--- /dev/null
+++ b/ReactWindows/ReactNative/UIManager/TouchTargetHelper.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Windows.UI.Xaml.Controls;
+
+namespace ReactNative.UIManager
+{
+ class TouchTargetHelper
+ {
+ internal static int FindTargetTagForTouch(double touchX, double touchY, Panel view)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/ReactWindows/ReactNative/UIManager/UIImplementation.cs b/ReactWindows/ReactNative/UIManager/UIImplementation.cs
index b0b39408252..b7c34857181 100644
--- a/ReactWindows/ReactNative/UIManager/UIImplementation.cs
+++ b/ReactWindows/ReactNative/UIManager/UIImplementation.cs
@@ -280,7 +280,7 @@ public void ManageChildren(
// them. Like the view removal, iteration direction is important
// to preserve the correct index.
- Array.Sort(viewsToAdd, ViewAtIndex.Comparer);
+ Array.Sort(viewsToAdd, ViewAtIndex.IndexComparer);
Array.Sort(indicesToRemove);
// Apply changes to the ReactShadowNode hierarchy.
@@ -493,7 +493,7 @@ public void DispatchViewManagerCommand(int reactTag, int commandId, JArray comma
/// Callback used with the position of the selected item as the first
/// argument, or no arguments if the menu is dismissed.
///
- public void ShowPopupMenu(int reactTag, JArray items, ICallback error, ICallback success)
+ public void ShowPopupMenu(int reactTag, string[] items, ICallback error, ICallback success)
{
AssertViewExists(reactTag);
_operationsQueue.EnqueueShowPopupMenu(reactTag, items, error, success);
diff --git a/ReactWindows/ReactNative/UIManager/UIManagerModule.cs b/ReactWindows/ReactNative/UIManager/UIManagerModule.cs
index d1d49e216f7..dea8fc6c66c 100644
--- a/ReactWindows/ReactNative/UIManager/UIManagerModule.cs
+++ b/ReactWindows/ReactNative/UIManager/UIManagerModule.cs
@@ -313,7 +313,7 @@ public void dispatchViewManagerCommand(int reactTag, int commandId, JArray comma
/// argument, or no arguments if the menu is dismissed.
///
[ReactMethod]
- public void showPopupMenu(int reactTag, JArray items, ICallback error, ICallback success)
+ public void showPopupMenu(int reactTag, string[] items, ICallback error, ICallback success)
{
_uiImplementation.ShowPopupMenu(reactTag, items, error, success);
}
diff --git a/ReactWindows/ReactNative/UIManager/UIViewOperationQueue.cs b/ReactWindows/ReactNative/UIManager/UIViewOperationQueue.cs
index 5ead5e8cfc5..2dead432af9 100644
--- a/ReactWindows/ReactNative/UIManager/UIViewOperationQueue.cs
+++ b/ReactWindows/ReactNative/UIManager/UIViewOperationQueue.cs
@@ -96,7 +96,7 @@ public void EnqueueDispatchCommand(int tag, int command, JArray args)
EnqueueOperation(() => _nativeViewHierarchyManager.DispatchCommand(tag, command, args));
}
- public void EnqueueShowPopupMenu(int tag, JArray items, ICallback error, ICallback success)
+ public void EnqueueShowPopupMenu(int tag, string[] items, ICallback error, ICallback success)
{
EnqueueOperation(() => _nativeViewHierarchyManager.ShowPopupMenu(tag, items, success));
}
@@ -134,7 +134,7 @@ public void EnqueueCreateView(
public void EnqueueUpdateProperties(int tag, string className, CatalystStylesDiffMap properties)
{
EnqueueOperation(() =>
- _nativeViewHierarchyManager.UpdateProperties(tag, className, properties));
+ _nativeViewHierarchyManager.UpdateProperties(tag, properties));
}
internal void EnqueueManageChildren(
diff --git a/ReactWindows/ReactNative/UIManager/ViewAtIndex.cs b/ReactWindows/ReactNative/UIManager/ViewAtIndex.cs
index ff2cd19d91a..17519156b0d 100644
--- a/ReactWindows/ReactNative/UIManager/ViewAtIndex.cs
+++ b/ReactWindows/ReactNative/UIManager/ViewAtIndex.cs
@@ -2,19 +2,36 @@
namespace ReactNative.UIManager
{
- class ViewAtIndex
+ ///
+ /// A data structure for holding tags and indices.
+ ///
+ public class ViewAtIndex
{
+ ///
+ /// Instantiates the .
+ ///
+ /// The tag.
+ /// The index.
public ViewAtIndex(int tag, int index)
{
Tag = tag;
Index = index;
}
- public static IComparer Comparer { get; } =
+ ///
+ /// A comparer for instances to sort by index.
+ ///
+ public static IComparer IndexComparer { get; } =
Comparer.Create((x, y) => x.Index - y.Index);
+ ///
+ /// The index of the view.
+ ///
public int Index { get; }
+ ///
+ /// The tag of the view.
+ ///
public int Tag { get; }
}
}
diff --git a/ReactWindows/ReactNative/UIManager/ViewGroupManager.cs b/ReactWindows/ReactNative/UIManager/ViewGroupManager.cs
index 38d774d56f0..605f1804077 100644
--- a/ReactWindows/ReactNative/UIManager/ViewGroupManager.cs
+++ b/ReactWindows/ReactNative/UIManager/ViewGroupManager.cs
@@ -82,9 +82,9 @@ public int GetChildCount(Panel parent)
/// The parent view.
/// The index.
/// The child view.
- public UIElement GetChildAt(Panel parent, int index)
+ public FrameworkElement GetChildAt(Panel parent, int index)
{
- return parent.Children[index];
+ return (FrameworkElement)parent.Children[index];
}
///
diff --git a/ReactWindows/ReactNative/UIManager/ViewManager.cs b/ReactWindows/ReactNative/UIManager/ViewManager.cs
index c5e18045934..552cbf03b66 100644
--- a/ReactWindows/ReactNative/UIManager/ViewManager.cs
+++ b/ReactWindows/ReactNative/UIManager/ViewManager.cs
@@ -95,7 +95,7 @@ public void UpdateProperties(FrameworkElement viewToUpdate, CatalystStylesDiffMa
/// The context.
/// The responder handler.
/// The view.
- public TFrameworkElement CreateView(
+ public FrameworkElement CreateView(
ThemedReactContext reactContext,
JavaScriptResponderHandler jsResponderHandler)
{
@@ -120,7 +120,7 @@ public TFrameworkElement CreateView(
///
/// Derived classes do not need to call this base method.
///
- public virtual void OnDropViewInstance(ThemedReactContext reactContext, TFrameworkElement view)
+ public virtual void OnDropViewInstance(ThemedReactContext reactContext, FrameworkElement view)
{
}
@@ -154,7 +154,7 @@ public virtual void OnDropViewInstance(ThemedReactContext reactContext, TFramewo
///
/// Identifer for the command.
/// Optional arguments for the command.
- public abstract void ReceiveCommand(TFrameworkElement root, int commandId, JArray args);
+ public abstract void ReceiveCommand(FrameworkElement root, int commandId, JArray args);
///
/// Creates a new view instance of type .