diff --git a/ReactNative.Tests/Internal/MockEvent.cs b/ReactNative.Tests/Internal/MockEvent.cs index 938a1688136..b31a0c42e1c 100644 --- a/ReactNative.Tests/Internal/MockEvent.cs +++ b/ReactNative.Tests/Internal/MockEvent.cs @@ -1,6 +1,7 @@ using Newtonsoft.Json.Linq; using ReactNative.UIManager.Events; using System; +using System.Collections.Generic; namespace ReactNative.Tests { diff --git a/ReactNative.Tests/UIManager/UIManagerModuleTests.cs b/ReactNative.Tests/UIManager/UIManagerModuleTests.cs index 836264bdb39..ef7baf8cbb9 100644 --- a/ReactNative.Tests/UIManager/UIManagerModuleTests.cs +++ b/ReactNative.Tests/UIManager/UIManagerModuleTests.cs @@ -125,6 +125,11 @@ public IReadOnlyDictionary NativeProperties return null; } } + + public ReactShadowNode CreateShadowNodeInstance() + { + return null; + } } } } diff --git a/ReactNative/ReactNative.csproj b/ReactNative/ReactNative.csproj index 4f64315bd3c..33d54ab617f 100644 --- a/ReactNative/ReactNative.csproj +++ b/ReactNative/ReactNative.csproj @@ -190,6 +190,8 @@ + + @@ -205,6 +207,8 @@ + + @@ -218,6 +222,7 @@ + diff --git a/ReactNative/UIManager/CatalystStylesDiffMap.cs b/ReactNative/UIManager/CatalystStylesDiffMap.cs new file mode 100644 index 00000000000..03661a80faf --- /dev/null +++ b/ReactNative/UIManager/CatalystStylesDiffMap.cs @@ -0,0 +1,14 @@ +using Newtonsoft.Json.Linq; + +namespace ReactNative.UIManager +{ + public class CatalystStylesDiffMap + { + private JObject properties; + + public CatalystStylesDiffMap(JObject properties) + { + this.properties = properties; + } + } +} \ No newline at end of file diff --git a/ReactNative/UIManager/Events/RCTEventEmitter.cs b/ReactNative/UIManager/Events/RCTEventEmitter.cs index b4981ea83a1..5cefa449ccc 100644 --- a/ReactNative/UIManager/Events/RCTEventEmitter.cs +++ b/ReactNative/UIManager/Events/RCTEventEmitter.cs @@ -1,5 +1,6 @@ using Newtonsoft.Json.Linq; using ReactNative.Bridge; +using System.Collections.Generic; namespace ReactNative.UIManager.Events { diff --git a/ReactNative/UIManager/IViewManager.cs b/ReactNative/UIManager/IViewManager.cs index 6cb31e7ce2e..74cdcc60ba5 100644 --- a/ReactNative/UIManager/IViewManager.cs +++ b/ReactNative/UIManager/IViewManager.cs @@ -36,5 +36,11 @@ public interface IViewManager /// The native properties. /// IReadOnlyDictionary NativeProperties { get; } + + /// + /// Creates a shadow node for the view manager. + /// + /// The shadow node instance. + ReactShadowNode CreateShadowNodeInstance(); } } diff --git a/ReactNative/UIManager/NativeViewHierarchyOptimizer.cs b/ReactNative/UIManager/NativeViewHierarchyOptimizer.cs new file mode 100644 index 00000000000..1018b346311 --- /dev/null +++ b/ReactNative/UIManager/NativeViewHierarchyOptimizer.cs @@ -0,0 +1,43 @@ +using System; + +namespace ReactNative.UIManager +{ + class NativeViewHierarchyOptimizer + { + private readonly UIViewOperationQueue _uiViewOperationQueue; + private readonly ShadowNodeRegistry _shadowNodeRegistry; + + public NativeViewHierarchyOptimizer( + UIViewOperationQueue uiViewOperationQueue, + ShadowNodeRegistry shadowNodeRegistry) + { + _uiViewOperationQueue = uiViewOperationQueue; + _shadowNodeRegistry = shadowNodeRegistry; + } + + internal void OnBatchComplete() + { + throw new NotImplementedException(); + } + + internal void HandleCreateView(ReactShadowNode cssNode, ThemedReactContext themedContext, CatalystStylesDiffMap styles) + { + throw new NotImplementedException(); + } + + internal void HandleUpdateView(ReactShadowNode cssNode, string className, CatalystStylesDiffMap styles) + { + throw new NotImplementedException(); + } + + internal void HandleManageChildren(ReactShadowNode cssNodeToManage, int[] indicesToRemove, int[] tagsToRemove, ViewAtIndex[] viewsToAdd, int[] tagsToDelete) + { + throw new NotImplementedException(); + } + + internal void HandleRemoveNode(ReactShadowNode nodeToRemove) + { + throw new NotImplementedException(); + } + } +} diff --git a/ReactNative/UIManager/OnLayoutEvent.cs b/ReactNative/UIManager/OnLayoutEvent.cs new file mode 100644 index 00000000000..2d94761b3ab --- /dev/null +++ b/ReactNative/UIManager/OnLayoutEvent.cs @@ -0,0 +1,48 @@ +using Newtonsoft.Json.Linq; +using ReactNative.UIManager.Events; +using System; + +namespace ReactNative.UIManager +{ + class OnLayoutEvent : Event + { + private int _x; + private int _y; + private int _width; + private int _height; + + private OnLayoutEvent(int viewTag, int x, int y, int width, int height) + : base(viewTag, TimeSpan.FromTicks(Environment.TickCount)) + { + _x = x; + _y = y; + _width = width; + _height = height; + } + + public override string EventName + { + get + { + return "topLayout"; + } + } + + public override void Dispatch(RCTEventEmitter eventEmitter) + { + var eventArgs = new JObject + { + { "target", ViewTag }, + { "layout", null /* TODO: create layout arguments */ }, + }; + + eventEmitter.receiveEvent(ViewTag, EventName, eventArgs); + } + + public static OnLayoutEvent Obtain(int viewTag, int x, int y, int width, int height) + { + // TODO: Introduce pooling mechanism + return new OnLayoutEvent(viewTag, x, y, width, height); + } + } +} diff --git a/ReactNative/UIManager/ReactShadowNode.cs b/ReactNative/UIManager/ReactShadowNode.cs index fd54648c40b..7281106df78 100644 --- a/ReactNative/UIManager/ReactShadowNode.cs +++ b/ReactNative/UIManager/ReactShadowNode.cs @@ -1,12 +1,13 @@ - -using ReactNative.csslayout; +using ReactNative.CSSLayout; using System.Collections.Generic; +using System; +using ReactNative.UIManager.Events; namespace ReactNative.UIManager { public class ReactShadowNode : CSSNode { - private int _ReactTag; + private int _reactTag; private float _AbsoluteLeft; private float _AbsoluteTop; private float _AbsoluteRight; @@ -18,9 +19,94 @@ public class ReactShadowNode : CSSNode private ReactShadowNode mNativeParent; private List mNativeChildren; - public int getReactTag() + public int ReactTag { - return _ReactTag; + get; + set; + } + + public string ViewClassName + { + get; + set; + } + public int StyleWidth + { + get; + set; + } + + public int StyleHeight + { + get; + set; + } + public ReactShadowNode RootNode + { + get; + set; + } + + public ReactShadowNode Parent + { + get; + set; + } + + public ThemedReactContext ThemedContext + { + get; + set; + } + public bool IsVirtual { get; internal set; } + + public void UpdateProperties(CatalystStylesDiffMap styles) + { + throw new NotImplementedException(); + } + + internal IList Children + { + get + { + throw new NotImplementedException(); + } + } + + public bool IsVirtualAnchor { get; internal set; } + public bool IsLayoutOnly { get; internal set; } + public bool HasUpdates { get; internal set; } + public double LayoutX { get; internal set; } + public double LayoutY { get; internal set; } + public bool ShouldNotifyOnLayout { get; internal set; } + public int ScreenX { get; internal set; } + public int ScreenWidth { get; internal set; } + public int ScreenHeight { get; internal set; } + public int ScreenY { get; internal set; } + + internal int IndexOf(ReactShadowNode oldNode) + { + throw new NotImplementedException(); + } + + internal void CalculateLayout(CSSLayoutContext _layoutContext) + { + throw new NotImplementedException(); + } + + internal void OnBeforeLayout() + { + throw new NotImplementedException(); + } + + internal void DispatchUpdates(double absoluteX, double absoluteY, EventDispatcher eventDispatcher, NativeViewHierarchyOptimizer _nativeViewHierarchyOptimizer) + { + throw new NotImplementedException(); + } + + internal void MarkUpdateSeen() + { + throw new NotImplementedException(); } } } diff --git a/ReactNative/UIManager/ShadowNodeRegistry.cs b/ReactNative/UIManager/ShadowNodeRegistry.cs index a946587038e..79e344ffb00 100644 --- a/ReactNative/UIManager/ShadowNodeRegistry.cs +++ b/ReactNative/UIManager/ShadowNodeRegistry.cs @@ -1,72 +1,84 @@  using System; using System.Collections.Generic; +using System.Globalization; namespace ReactNative.UIManager { /// - /// Simple container class to keep track of s associated with a particular - /// instance. + /// Simple container class to keep track of s + /// associated with a particular instance. /// class ShadowNodeRegistry { - private readonly Dictionary _TagsToCSSNodes; - private readonly Dictionary _RootTags; + private readonly IDictionary _tagsToCssNodes = + new Dictionary(); - public ShadowNodeRegistry() + private readonly IDictionary _rootTags = + new Dictionary(); + + public ICollection RootNodeTags { - _TagsToCSSNodes = new Dictionary(); - _RootTags = new Dictionary(); - } + get + { + return _rootTags.Keys; + } + } - public void addRootNode(ReactShadowNode node) + public void AddRootNode(ReactShadowNode node) { - int tag = node.getReactTag(); - _TagsToCSSNodes.Add(tag, node); - _RootTags.Add(tag, true); + var tag = node.ReactTag; + _tagsToCssNodes[tag] = node; + _rootTags[tag] = true; } - public void removeRootNode(int tag) + public void RemoveRootNode(int tag) { - if (!_RootTags.ContainsKey(tag)) + if (!_rootTags.ContainsKey(tag)) { throw new InvalidOperationException( - "View with tag " + tag + " is not registered as a root view"); + "View with tag " + tag + " is not registered as a root view."); } - _TagsToCSSNodes.Remove(tag); - _RootTags.Remove(tag); + _tagsToCssNodes.Remove(tag); + _rootTags.Remove(tag); } - public void addNode(ReactShadowNode node) + public void AddNode(ReactShadowNode node) { - _TagsToCSSNodes.Add(node.getReactTag(), node); + _tagsToCssNodes[node.ReactTag] = node; } - public void removeNode(int tag) + public void RemoveNode(int tag) { - if (_RootTags[tag]) + var isRoot = default(bool); + if (_rootTags.TryGetValue(tag, out isRoot) && isRoot) { throw new InvalidOperationException( - "Trying to remove root node " + tag + " without using removeRootNode!"); + "Trying to remove root node " + tag + " without using RemoveRootNode."); } - _TagsToCSSNodes.Remove(tag); - } - public ReactShadowNode getNode(int tag) - { - return _TagsToCSSNodes[tag]; + _tagsToCssNodes.Remove(tag); } - public bool isRootNode(int tag) + public ReactShadowNode GetNode(int tag) { - return _RootTags.ContainsKey(tag); + var result = default(ReactShadowNode); + if (_tagsToCssNodes.TryGetValue(tag, out result)) + { + return result; + } + + throw new KeyNotFoundException( + string.Format( + CultureInfo.InvariantCulture, + "Shadow node for tag '{0}' does not exist.", + tag)); } - public int getRootNodeCount() + public bool IsRootNode(int tag) { - return _RootTags.Count; + return _rootTags.ContainsKey(tag); } - } } diff --git a/ReactNative/UIManager/UIImplementation.cs b/ReactNative/UIManager/UIImplementation.cs index f742e35cc91..c1f0ecce586 100644 --- a/ReactNative/UIManager/UIImplementation.cs +++ b/ReactNative/UIManager/UIImplementation.cs @@ -1,136 +1,646 @@ using Newtonsoft.Json.Linq; using ReactNative.Bridge; +using ReactNative.CSSLayout; +using ReactNative.Tracing; using ReactNative.UIManager.Events; using System; using System.Collections.Generic; +using System.Globalization; +using System.Runtime.CompilerServices; namespace ReactNative.UIManager { /// - /// An class that is used to receive React commands from JS and translate them into a - /// shadow node hierarchy that is then mapped to a native view hierarchy. + /// An class that is used to receive React commands from JavaScript and + /// translate them into a shadow node hierarchy that is then mapped to a + /// native view hierarchy. /// /// TODOS /// 1. CSSLayoutContext - /// 2. Implement _ViewManagers registry - /// 3. Create ShadowNodeRegistry - /// 4. View reigstration for root and children - /// 5. Shadow dom item updates + /// 2. View registration for root and children + /// 3. Shadow DOM item updates + /// 4. Animation support /// public class UIImplementation { private readonly ViewManagerRegistry _viewManagers; private readonly UIViewOperationQueue _operationsQueue; - private readonly ShadowNodeRegistry _shadowNodeRegistry = new ShadowNodeRegistry(); + private readonly ShadowNodeRegistry _shadowNodeRegistry; + private readonly NativeViewHierarchyOptimizer _nativeViewHierarchyOptimizer; + private readonly CSSLayoutContext _layoutContext; - public UIImplementation( - ReactApplicationContext reactContext, - IReadOnlyList viewManagers) + /// + /// Instantiates the . + /// + /// The react context. + /// The view managers. + public UIImplementation(ReactApplicationContext reactContext, IReadOnlyList viewManagers) + : this(reactContext, new ViewManagerRegistry(viewManagers)) + { + } + + private UIImplementation(ReactApplicationContext reactContext, ViewManagerRegistry viewManagers) + : this( + viewManagers, + new UIViewOperationQueue(reactContext, new NativeViewHierarchyManager(viewManagers))) { - _viewManagers = new ViewManagerRegistry(viewManagers); - _operationsQueue = new UIViewOperationQueue(reactContext, new NativeViewHierarchyManager(_viewManagers)); } /// - /// Called when the host receives the suspend event. + /// Instantiates the . /// - public void OnSuspend() + /// The view managers. + /// The operations queue. + protected UIImplementation( + ViewManagerRegistry viewManagers, + UIViewOperationQueue operationsQueue) { - throw new NotImplementedException(); + _viewManagers = viewManagers; + _operationsQueue = operationsQueue; + _shadowNodeRegistry = new ShadowNodeRegistry(); + _nativeViewHierarchyOptimizer = new NativeViewHierarchyOptimizer( + _operationsQueue, + _shadowNodeRegistry); } /// - /// Called when the host receives the resume event. + /// Register the root view. /// - public void OnResume() + /// The root view. + /// The view tag. + /// The width. + /// The height. + /// The context. + public void RegisterRootView( + SizeMonitoringFrameLayout rootView, + int tag, + int width, + int height, + ThemedReactContext context) { - throw new NotImplementedException(); + var rootCssNode = CreateRootShadowNode(); + rootCssNode.ReactTag = tag; + rootCssNode.StyleWidth = width; + rootCssNode.StyleHeight = height; + _shadowNodeRegistry.AddRootNode(rootCssNode); + + // Register it with the NativeViewHierarchyManager. + _operationsQueue.AddRootView(tag, rootView, context); } /// - /// Called when the host is shutting down. + /// Unregisters a root view with the given tag. /// - public void OnShutdown() + /// The root view tag. + public void RemoveRootView(int rootViewTag) + { + _shadowNodeRegistry.RemoveRootNode(rootViewTag); + _operationsQueue.EnqueueRemoveRootView(rootViewTag); + } + + /// + /// Invoked when the native view that corresponds to a root node has + /// its size changed. + /// + /// The root view tag. + /// The new width. + /// The new height. + /// The event dispatcher. + public void UpdateRootNodeSize( + int rootViewTag, + int newWidth, + int newHeight, + EventDispatcher eventDispatcher) + { + var rootCssNode = _shadowNodeRegistry.GetNode(rootViewTag); + rootCssNode.StyleWidth = newWidth; + rootCssNode.StyleHeight = newHeight; + + // If we're in the middle of a batch, the change will be + // automatically dispatched at the end of the batch. The event + // queue should always be empty, but that is an implementation + // detail. + if (_operationsQueue.IsEmpty()) + { + DispatchViewUpdates(eventDispatcher, -1 /* no associated batch id */); + } + } + + /// + /// Invoked by React to create a new node with the given tag, class + /// name, and properties. + /// + /// The view tag. + /// The class name. + /// The root view tag. + /// The properties. + public void CreateView(int tag, string className, int rootViewTag, JObject properties) + { + var cssNode = CreateShadowNode(className); + var rootNode = _shadowNodeRegistry.GetNode(rootViewTag); + cssNode.ReactTag = tag; + cssNode.ViewClassName = className; + cssNode.RootNode = rootNode; + cssNode.ThemedContext = rootNode.ThemedContext; + + _shadowNodeRegistry.AddNode(cssNode); + + var styles = default(CatalystStylesDiffMap); + if (properties != null) + { + styles = new CatalystStylesDiffMap(properties); + cssNode.UpdateProperties(styles); + } + + HandleCreateView(cssNode, rootViewTag, styles); + } + + /// + /// Invoked by React when the properties change for a node with the + /// given tag. + /// + /// The view tag. + /// The view class name. + /// The properties. + public void UpdateView(int tag, string className, JObject properties) + { + var viewManager = _viewManagers.Get(className); + var cssNode = _shadowNodeRegistry.GetNode(tag); + if (cssNode == null) + { + throw new InvalidOperationException( + string.Format( + CultureInfo.InvariantCulture, + "Trying to update view with invalid tag '{0}'.", + tag)); + } + + if (properties != null) + { + var styles = new CatalystStylesDiffMap(properties); + cssNode.UpdateProperties(styles); + HandleUpdateView(cssNode, className, styles); + } + } + + /// + /// Manage the children of a view. + /// + /// The view tag of the parent view. + /// + /// A list of indices in the parent view to move views from. + /// + /// + /// A list of indices in the parent view to move views to. + /// + /// + /// A list of tags of views to add to the parent. + /// + /// + /// A list of indices to insert the child tags at. + /// + /// + /// A list of indices to permanently remove. The memory for the + /// corresponding views and data structures should be reclaimed. + /// + public void ManageChildren( + int viewTag, + int[] moveFrom, + int[] moveTo, + int[] addChildTags, + int[] addAtIndices, + int[] removeFrom) + { + if (moveFrom?.Length != moveTo?.Length) + { + throw new ArgumentException( + "Size of 'moveFrom' does not equal size of 'moveTo'.", + nameof(moveFrom)); + } + + if (addChildTags?.Length != addAtIndices?.Length) + { + throw new ArgumentException( + "Size of 'addChildTags' does not equal size of 'addAtIndices'.", + nameof(addChildTags)); + } + + var cssNodeToManage = _shadowNodeRegistry.GetNode(viewTag); + var children = cssNodeToManage.Children; + + var numToMove = moveFrom?.Length ?? 0; + var numToAdd = addChildTags?.Length ?? 0; + var numToRemove = removeFrom?.Length ?? 0; + var viewsToAdd = new ViewAtIndex[numToMove + numToAdd]; + var indicesToRemove = new int[numToMove + numToRemove]; + var tagsToRemove = new int[addAtIndices.Length]; + var tagsToDelete = new int[numToRemove]; + + if (numToMove > 0) + { + for (var i = 0; i < numToMove; ++i) + { + var moveFromIndex = moveFrom[i]; + var tagToMove = children[moveFromIndex].ReactTag; + viewsToAdd[i] = new ViewAtIndex(tagToMove, moveTo[i]); + indicesToRemove[i] = moveFromIndex; + tagsToRemove[i] = tagToMove; + } + } + + if (numToAdd > 0) + { + for (var i = 0; i < numToRemove; ++i) + { + viewsToAdd[numToMove + i] = new ViewAtIndex(addChildTags[i], addAtIndices[i]); + } + } + + if (numToRemove > 0) + { + for (var i = 0; i < numToRemove; ++i) + { + var indexToRemove = removeFrom[i]; + var tagToRemove = children[indexToRemove].ReactTag; + indicesToRemove[numToRemove + i] = indexToRemove; + tagsToRemove[numToRemove + i] = tagToRemove; + tagsToDelete[i] = tagToRemove; + } + } + + // NB: moveFrom and removeForm are both relative to the starting + // state of the view's children. + // + // 1) Sort the views to add and indices to remove by index + // 2) Iterate the indices being removed from high to low and remove + // them. Going high to low makes sure we remove the correct + // index when there are multiple to remove. + // 3) Iterate the views being added by index low to high and add + // them. Like the view removal, iteration direction is important + // to preserve the correct index. + + Array.Sort(viewsToAdd, ViewAtIndex.Comparer); + Array.Sort(indicesToRemove); + + // Apply changes to the ReactShadowNode hierarchy. + var lastIndexRemoved = -1; + for (var i = indicesToRemove.Length - 1; i >= 0; --i) + { + var indexToRemove = indicesToRemove[i]; + if (indexToRemove == lastIndexRemoved) + { + throw new InvalidOperationException( + string.Format( + CultureInfo.InvariantCulture, + "Repeated indices in removal list for view tag '{0}'.", + viewTag)); + } + + children.RemoveAt(indexToRemove); + lastIndexRemoved = indexToRemove; + } + + for (var i = 0; i < viewsToAdd.Length; ++i) + { + var viewAtIndex = viewsToAdd[i]; + var cssNodeToAdd = _shadowNodeRegistry.GetNode(viewAtIndex.Tag); + if (cssNodeToAdd == null) + { + throw new InvalidOperationException( + string.Format( + CultureInfo.InvariantCulture, + "Trying to add unknown view tag '{0}'.", + viewAtIndex.Tag)); + } + + children.Insert(viewAtIndex.Index, cssNodeToAdd); + } + + if (!cssNodeToManage.IsVirtual && !cssNodeToManage.IsVirtualAnchor) + { + _nativeViewHierarchyOptimizer.HandleManageChildren( + cssNodeToManage, + indicesToRemove, + tagsToRemove, + viewsToAdd, + tagsToDelete); + } + + for (var i = 0; i < tagsToDelete.Length; ++i) + { + RemoveShadowNode(_shadowNodeRegistry.GetNode(tagsToDelete[i])); + } + } + + /// + /// Replaces the view specified by with the + /// view specified by within + /// 's parent. + /// + /// The old tag. + /// The new tag. + public void ReplaceExistingNonRootView(int oldTag, int newTag) + { + if (_shadowNodeRegistry.IsRootNode(oldTag) || _shadowNodeRegistry.IsRootNode(newTag)) + { + throw new InvalidOperationException("Cannot add or replace a root tag."); + } + + var oldNode = _shadowNodeRegistry.GetNode(oldTag); + var parent = oldNode.Parent; + if (parent == null) + { + throw new InvalidOperationException( + string.Format( + CultureInfo.InvariantCulture, + "Node '{0}' is not attached to a parent.", + oldTag)); + } + + var oldIndex = parent.IndexOf(oldNode); + if (oldIndex < 0) + { + throw new InvalidOperationException("Did not find child tag in parent."); + } + + var tagsToAdd = new[] { newTag }; + var addAtIndices = new[] { oldIndex }; + var indicesToRemove = new[] { oldIndex }; + + ManageChildren(parent.ReactTag, null, null, tagsToAdd, addAtIndices, indicesToRemove); + } + + /// + /// Method which takes a container tag and then releases all subviews + /// for that container upon receipt. + /// + /// The container tag. + public void RemoveSubviewsFromContainerWithID(int containerTag) + { + var containerNode = _shadowNodeRegistry.GetNode(containerTag); + var n = containerNode.Children.Count; + var indicesToRemove = new int[n]; + for (var i = 0; i < n; ++i) + { + indicesToRemove[i] = i; + } + + ManageChildren(containerTag, null, null, null, null, indicesToRemove); + } + + /// + /// Determines the location on screen, width, and height of the given + /// view and returns the values via an asynchronous callback. + /// + /// The view tag to measure. + /// The callback. + public void Measure(int reactTag, ICallback callback) { + _operationsQueue.EnqueueMeasure(reactTag, callback); + } + + internal void MeasureLayout(int tag, int ancestorTag, ICallback errorCallback, ICallback successCallback) + { + // TODO throw new NotImplementedException(); } - internal void DispatchViewUpdates(EventDispatcher _eventDispatcher, int batchId) + internal void MeasureLayoutRelativeToParent(int tag, ICallback errorCallback, ICallback successCallback) { + // TODO throw new NotImplementedException(); } + /// + /// Invoked at the end of a transaction to commit any updates to the + /// node hierarchy. + /// + /// The event dispatcher. + /// The batch identifier. + public void DispatchViewUpdates(EventDispatcher eventDispatcher, int batchId) + { + foreach (var tag in _shadowNodeRegistry.RootNodeTags) + { + var cssRoot = _shadowNodeRegistry.GetNode(tag); + NotifyBeforeOnLayoutRecursive(cssRoot); + CalculateRootLayout(cssRoot); + ApplyUpdatesRecursive(cssRoot, 0, 0, eventDispatcher); + } + + _nativeViewHierarchyOptimizer.OnBatchComplete(); + _operationsQueue.DispatchViewUpdates(batchId); + } + internal void ConfigureNextLayoutAnimation(Dictionary config, ICallback success, ICallback error) { + // TODO throw new NotImplementedException(); } - internal void SetLayoutAnimationEnabledExperimental(bool enabled) + /// + /// Sets a JavaScript responder for a view. + /// + /// The view ID. + /// + /// Flag to signal if the native responder should be blocked. + /// + public void SetJavaScriptResponder(int reactTag, bool blockNativeResponder) { - throw new NotImplementedException(); + AssertViewExists(reactTag); + var node = _shadowNodeRegistry.GetNode(reactTag); + while (node.IsVirtual || node.IsLayoutOnly) + { + node = node.Parent; + } + + _operationsQueue.EnqueueSetJavaScriptResponder(node.ReactTag, reactTag, blockNativeResponder); } - internal void ShowPopupMenu(int reactTag, JArray items, ICallback error, ICallback success) + /// + /// Clears the JavaScript responder. + /// + public void ClearJavaScriptResponder() { - throw new NotImplementedException(); + _operationsQueue.EnqueueClearJavaScriptResponder(); } - internal void DispatchViewManagerCommand(int reactTag, int commandId, JArray commandArgs) + /// + /// Dispatches a command to the view manager. + /// + /// The tag of the view manager. + /// The command ID. + /// The command arguments. + public void DispatchViewManagerCommand(int reactTag, int commandId, JArray commandArgs) { - throw new NotImplementedException(); + AssertViewExists(reactTag); + _operationsQueue.EnqueueDispatchViewManagerCommand(reactTag, commandId, commandArgs); } - internal void RemoveRootView(int rootViewTag) + /// + /// Show a pop-up menu. + /// + /// + /// The tag of the anchor view (the pop-up menu 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. + /// + /// Callback used if there is an error displaying the menu. + /// + /// + /// 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) { - throw new NotImplementedException(); + AssertViewExists(reactTag); + _operationsQueue.EnqueueShowPopupMenu(reactTag, items, error, success); } - internal void ClearJavaScriptResponder() + /// + /// Called when the host receives the suspend event. + /// + public void OnSuspend() { - throw new NotImplementedException(); + _operationsQueue.SuspendFrameCallback(); } - internal void SetJavaScriptResponder(int reactTag, bool blockNativeResponder) + /// + /// Called when the host receives the resume event. + /// + public void OnResume() { - throw new NotImplementedException(); + _operationsQueue.ResumeFrameCallback(); } - internal void CreateView(int tag, string className, int rootViewTag, JObject props) + /// + /// Called when the host is shutting down. + /// + public void OnShutdown() { - throw new NotImplementedException(); } - internal void UpdateView(int tag, string className, JObject props) + private void HandleCreateView(ReactShadowNode cssNode, int rootViewTag, CatalystStylesDiffMap styles) { - throw new NotImplementedException(); + if (!cssNode.IsVirtual) + { + _nativeViewHierarchyOptimizer.HandleCreateView(cssNode, cssNode.ThemedContext, styles); + } } - internal void ManageChildren(int viewTag, JArray moveFrom, JArray moveTo, JArray addChildTags, JArray addAtIndices, JArray removeFrom) + private void HandleUpdateView( + ReactShadowNode cssNode, + string className, + CatalystStylesDiffMap styles) { - throw new NotImplementedException(); + if (!cssNode.IsVirtual) + { + _nativeViewHierarchyOptimizer.HandleUpdateView(cssNode, className, styles); + } } - internal void ReplaceExistingNonRootView(int oldTag, int newTag) + private ReactShadowNode CreateRootShadowNode() { - throw new NotImplementedException(); + var rootCssNode = new ReactShadowNode(); + rootCssNode.ViewClassName = "Root"; + return rootCssNode; } - internal void RemoveSubviewsFromContainerWithID(int containerTag) + private ReactShadowNode CreateShadowNode(string className) { - throw new NotImplementedException(); + var viewManager = _viewManagers.Get(className); + return viewManager.CreateShadowNodeInstance(); } - internal void Measure(int reactTag, ICallback callback) + private ReactShadowNode ResolveShadowNode(int reactTag) { - throw new NotImplementedException(); + return _shadowNodeRegistry.GetNode(reactTag); } - internal void MeasureLayout(int tag, int ancestorTag, ICallback errorCallback, ICallback successCallback) + private void RemoveShadowNode(ReactShadowNode nodeToRemove) { - throw new NotImplementedException(); + _nativeViewHierarchyOptimizer.HandleRemoveNode(nodeToRemove); + _shadowNodeRegistry.RemoveNode(nodeToRemove.ReactTag); + foreach (var child in nodeToRemove.Children) + { + RemoveShadowNode(child); + } + + nodeToRemove.Children.Clear(); } - internal void MeasureLayoutRelativeToParent(int tag, ICallback errorCallback, ICallback successCallback) + private IViewManager ResolveViewManager(string className) + { + return _viewManagers.Get(className); + } + + private void NotifyBeforeOnLayoutRecursive(ReactShadowNode cssNode) + { + foreach (var child in cssNode.Children) + { + NotifyBeforeOnLayoutRecursive(child); + } + + cssNode.OnBeforeLayout(); + } + + private void CalculateRootLayout(ReactShadowNode cssRoot) + { + using (Tracer.Trace(Tracer.TRACE_TAG_REACT_BRIDGE, "ReactShadowNode.CalculateLayout") + .With("RootTag", cssRoot.ReactTag)) + { + cssRoot.CalculateLayout(_layoutContext); + } + } + + private void ApplyUpdatesRecursive( + ReactShadowNode cssNode, + double absoluteX, + double absoluteY, + EventDispatcher eventDispatcher) + { + if (!cssNode.HasUpdates) + { + return; + } + + if (!cssNode.IsVirtualAnchor) + { + foreach (var child in cssNode.Children) + { + ApplyUpdatesRecursive( + child, + absoluteX + cssNode.LayoutX, + absoluteY + cssNode.LayoutY, + eventDispatcher); + } + } + + var tag = cssNode.ReactTag; + if (!_shadowNodeRegistry.IsRootNode(tag)) + { + cssNode.DispatchUpdates( + absoluteX, + absoluteY, + eventDispatcher, + _nativeViewHierarchyOptimizer); + + if (cssNode.ShouldNotifyOnLayout) + { + OnLayoutEvent.Obtain( + tag, + cssNode.ScreenX, + cssNode.ScreenY, + cssNode.ScreenWidth, + cssNode.ScreenHeight); + } + } + + cssNode.MarkUpdateSeen(); + } + + private void AssertViewExists(int reactTag, [CallerMemberName]string caller = null) { throw new NotImplementedException(); } diff --git a/ReactNative/UIManager/UIManagerModule.cs b/ReactNative/UIManager/UIManagerModule.cs index 479fd994f7c..d1d49e216f7 100644 --- a/ReactNative/UIManager/UIManagerModule.cs +++ b/ReactNative/UIManager/UIManagerModule.cs @@ -160,11 +160,11 @@ public void updateView(int tag, string className, JObject props) [ReactMethod] public void manageChildren( int viewTag, - JArray moveFrom, - JArray moveTo, - JArray addChildTags, - JArray addAtIndices, - JArray removeFrom) + int[] moveFrom, + int[] moveTo, + int[] addChildTags, + int[] addAtIndices, + int[] removeFrom) { _uiImplementation.ManageChildren( viewTag, diff --git a/ReactNative/UIManager/UIViewOperationQueue.cs b/ReactNative/UIManager/UIViewOperationQueue.cs index 48643ccc3a4..3f7183ef420 100644 --- a/ReactNative/UIManager/UIViewOperationQueue.cs +++ b/ReactNative/UIManager/UIViewOperationQueue.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using Newtonsoft.Json.Linq; namespace ReactNative.UIManager { @@ -48,5 +49,59 @@ public UIViewOperationQueue(ReactApplicationContext reactContext, NativeViewHier _ReactApplicationContext = reactContext; } + internal void DispatchViewUpdates(int batchId) + { + throw new NotImplementedException(); + } + + internal void AddRootView(int tag, SizeMonitoringFrameLayout rootView, ThemedReactContext context) + { + throw new NotImplementedException(); + } + + internal void EnqueueRemoveRootView(int rootViewTag) + { + throw new NotImplementedException(); + } + + internal bool IsEmpty() + { + throw new NotImplementedException(); + } + + internal void EnqueueMeasure(int reactTag, ICallback callback) + { + throw new NotImplementedException(); + } + + internal void EnqueueSetJavaScriptResponder(int reactTag1, int reactTag2, bool blockNativeResponder) + { + throw new NotImplementedException(); + } + + internal void EnqueueClearJavaScriptResponder() + { + throw new NotImplementedException(); + } + + internal void EnqueueDispatchViewManagerCommand(int reactTag, int commandId, JArray commandArgs) + { + throw new NotImplementedException(); + } + + internal void EnqueueShowPopupMenu(int reactTag, JArray items, ICallback error, ICallback success) + { + throw new NotImplementedException(); + } + + internal void SuspendFrameCallback() + { + throw new NotImplementedException(); + } + + internal void ResumeFrameCallback() + { + throw new NotImplementedException(); + } } } diff --git a/ReactNative/UIManager/ViewAtIndex.cs b/ReactNative/UIManager/ViewAtIndex.cs new file mode 100644 index 00000000000..aa3aa6439ae --- /dev/null +++ b/ReactNative/UIManager/ViewAtIndex.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; + +namespace ReactNative.UIManager +{ + class ViewAtIndex + { + public ViewAtIndex(int tag, int index) + { + Tag = tag; + Index = index; + } + + public static IComparer Comparer { get; } = + Comparer.Create((x, y) => + { + return x.Index - y.Index; + + }); + + public int Index { get; } + + public int Tag { get; } + } +} diff --git a/ReactNative/UIManager/ViewManager.cs b/ReactNative/UIManager/ViewManager.cs index 0e388ffb599..344f7217818 100644 --- a/ReactNative/UIManager/ViewManager.cs +++ b/ReactNative/UIManager/ViewManager.cs @@ -57,6 +57,8 @@ public abstract class ViewManager : IViewManager public abstract IReadOnlyDictionary NativeProperties { get; } + public abstract ReactShadowNode CreateShadowNodeInstance(); + /// /// Called when view is detached from view hierarchy and allows for some additional cleanup by the {@link ViewManager} subclass. /// diff --git a/ReactNative/UIManager/ViewManagerRegistry.cs b/ReactNative/UIManager/ViewManagerRegistry.cs index 3e905483056..756c774ae42 100644 --- a/ReactNative/UIManager/ViewManagerRegistry.cs +++ b/ReactNative/UIManager/ViewManagerRegistry.cs @@ -1,35 +1,49 @@ - +using System; +using System.Collections.Generic; + namespace ReactNative.UIManager { - using UIManager; - using System; - using System.Collections.Generic; - using Windows.UI.Xaml; - + /// + /// Class that stores the mapping between the native view name used in + /// JavaScript and the instance of . + /// public class ViewManagerRegistry { - private readonly Dictionary mViewManagers = - new Dictionary(); + private readonly IDictionary _registry; - public ViewManagerRegistry(IReadOnlyList viewManagerList) + /// + /// Instantiates the . + /// + /// + /// The view managers to include in the registry. + /// + public ViewManagerRegistry(IReadOnlyList viewManagers) { - foreach (var viewManager in viewManagerList) + if (viewManagers == null) + throw new ArgumentNullException(nameof(viewManagers)); + + _registry = new Dictionary(); + + foreach (var viewManager in viewManagers) { - mViewManagers.Add(viewManager.Name, viewManager); + _registry.Add(viewManager.Name, viewManager); } } - public IViewManager get(string className) + /// + /// Gets the view manager for the given class name. + /// + /// The view manager class name. + /// The view manager. + public IViewManager Get(string className) { - var viewManager = mViewManagers[className]; - if (viewManager != null) + var viewManager = default(IViewManager); + if (_registry.TryGetValue(className, out viewManager)) { return viewManager; } - else - { - throw new ArgumentException("No ViewManager defined for class " + className); - } + + throw new ArgumentException("No view manager defined for class " + className, nameof(className)); } } } diff --git a/ReactNative/csslayout/CSSLayoutContext.cs b/ReactNative/csslayout/CSSLayoutContext.cs new file mode 100644 index 00000000000..031b027d2c3 --- /dev/null +++ b/ReactNative/csslayout/CSSLayoutContext.cs @@ -0,0 +1,6 @@ +namespace ReactNative.CSSLayout +{ + internal class CSSLayoutContext + { + } +} \ No newline at end of file diff --git a/ReactNative/csslayout/CSSNode.cs b/ReactNative/csslayout/CSSNode.cs index 0a3a643a45b..2350efc1fef 100644 --- a/ReactNative/csslayout/CSSNode.cs +++ b/ReactNative/csslayout/CSSNode.cs @@ -1,5 +1,5 @@  -namespace ReactNative.csslayout +namespace ReactNative.CSSLayout { public class CSSNode {