/// <summary> /// Handles the creation of a view. /// </summary> /// <param name="node">The shadow node for the view.</param> /// <param name="themedContext">The themed context.</param> /// <param name="initialProperties"> /// The initial properties for the view. /// </param> public void HandleCreateView( ReactShadowNode node, ThemedReactContext themedContext, ReactStylesDiffMap initialProperties) { #if !ENABLED _uiViewOperationQueue.EnqueueCreateView( themedContext, node.ReactTag, node.ViewClass, initialProperties); #else var isLayoutOnly = node.ViewClass == ViewProps.ViewClassName && IsLayoutOnlyAndCollapsible(initialProperties); node.IsLayoutOnly = isLayoutOnly; if (!isLayoutOnly) { _uiViewOperationQueue.EnqueueCreateView( themedContext, node.ReactTag, node.ViewClass, initialProperties); } #endif }
protected sealed override Inline Make(ReactShadowNode node, IList <Inline> children) { var textNode = node as ReactInlineShadowNode; if (textNode != null) { return(textNode.MakeInline(children)); } if (node.StyleWidth.Unit != YogaUnit.Point || node.StyleHeight.Unit != YogaUnit.Point) { throw new InvalidOperationException("Inline views embedded in text must have absolute dimensions."); } var rectangle = new Rectangle { Width = node.StyleWidth.Value, Height = node.StyleHeight.Value, }; return(new InlineUIContainer { Child = rectangle, }); }
public async Task ShadowRegistryNode_AddRootNode_WhileEnumerating() { var n1 = new ReactShadowNode { ReactTag = 1 }; var n2 = new ReactShadowNode { ReactTag = 2 }; var enter = new AutoResetEvent(false); var exit = new AutoResetEvent(false); var registry = new ShadowNodeRegistry(); var task = Task.Run(() => { enter.WaitOne(); registry.AddRootNode(n2); exit.Set(); }); registry.AddRootNode(n1); foreach (var tag in registry.RootNodeTags) { enter.Set(); exit.WaitOne(); } await task; Assert.AreEqual(2, registry.RootNodeTags.Count); }
private static void buildSpannedFromTextCSSNode(ReactTextShadowNode textShadowNode, ref string sb) { if (textShadowNode._textProperty != null) { sb += textShadowNode._textProperty; } if (textShadowNode._text != null) { sb += textShadowNode._text; } for (int i = 0, length = textShadowNode.TotalNativeChildren; i < length; i++) { ReactShadowNode child = textShadowNode.GetChildAt(i); if (child is ReactTextShadowNode) { buildSpannedFromTextCSSNode((ReactTextShadowNode)child, ref sb); } child.MarkUpdateSeen(); } if (textShadowNode._textProperty != null) { sb += _textPropertyTail; } }
/// <summary> /// Add a React shadow node. /// </summary> /// <param name="node">The node to add.</param> public void AddNode(ReactShadowNode node) { if (node == null) throw new ArgumentNullException(nameof(node)); _tagsToCssNodes[node.ReactTag] = node; }
/// <summary> /// Add a root shadow node. /// </summary> /// <param name="node">The node.</param> public void AddRootNode(ReactShadowNode node) { if (node == null) throw new ArgumentNullException(nameof(node)); var tag = node.ReactTag; _tagsToCssNodes[tag] = node; _rootTags[tag] = true; }
protected override Inline VisitCore(ReactShadowNode node) { var textNode = node as ReactInlineShadowNode; if (textNode != null) { return(base.VisitCore(node)); } return(Make(node, new List <Inline>())); }
/// <summary> /// Insert a child at the given index. /// </summary> /// <param name="child">The child.</param> /// <param name="index">The index.</param> public override void AddChildAt(ReactShadowNode child, int index) { base.AddChildAt(child, index); // Fixes a issue on Windows phone where rotating from horizontal to // vertical would leave a gap between the modal bottom and the screen edge. // This is set to the proper screen height when loading the modal in // ReactModalHostView.SetContentSize. The actual screen height cannot be // set here due to it being run by the layout manager thread which has no // active window and only mimics the main view bounds. child.StyleHeight = int.MaxValue; }
protected sealed override Inline Make(ReactShadowNode node, IList <Inline> children) { var textNode = node as ReactInlineShadowNode; if (textNode != null) { return(textNode.MakeInline(children)); } else { var rectangle = new Rectangle(); rectangle.Width = node.StyleWidth; rectangle.Height = node.StyleHeight; return(new InlineUIContainer { Child = rectangle, }); } }
private void AssertNodeDoesNotNeedCustomLayoutForChildren(ReactShadowNode node) { var viewManager = _viewManagers.Get(node.ViewClass); if (viewManager == null) { throw new InvalidOperationException( Invariant($"Could not find view class '{node.ViewClass}'.")); } var viewParentManager = viewManager as IViewParentManager; if (viewParentManager == null) { throw new InvalidOperationException( Invariant($"Trying to use view '{node.ViewClass}' as a parent but its manager is not a ViewParentManager.")); } if (viewParentManager.NeedsCustomLayoutForChildren) { throw new InvalidOperationException( string.Format( CultureInfo.InvariantCulture, "Trying to measure a view using measureLayout/measureLayoutRelativeToParent relative to " + "an ancestor that requires custom layout for it's children ('{0}'). Use measure instead.", node.ViewClass)); } }
private void MeasureLayoutRelativeToVerifiedAncestor( ReactShadowNode node, ReactShadowNode ancestor, double[] outputBuffer) { var offsetX = 0.0; var offsetY = 0.0; if (node != ancestor) { offsetX = node.LayoutX; offsetY = node.LayoutY; var current = node.Parent; while (current != ancestor) { Debug.Assert(current != null); AssertNodeDoesNotNeedCustomLayoutForChildren(current); offsetX += current.LayoutX; offsetY += current.LayoutY; current = current.Parent; } AssertNodeDoesNotNeedCustomLayoutForChildren(ancestor); } outputBuffer[0] = offsetX; outputBuffer[1] = offsetY; outputBuffer[2] = node.LayoutWidth; outputBuffer[3] = node.LayoutHeight; }
private void RemoveShadowNode(ReactShadowNode nodeToRemove) { _nativeViewHierarchyOptimizer.HandleRemoveNode(nodeToRemove); _shadowNodeRegistry.RemoveNode(nodeToRemove.ReactTag); for (var i = nodeToRemove.ChildCount - 1; i >= 0; --i) { RemoveShadowNode(nodeToRemove.GetChildAt(i)); } nodeToRemove.RemoveAllChildren(); }
private void RemoveNodeFromParent(ReactShadowNode nodeToRemove, bool shouldDelete) { var nativeNodeToRemoveFrom = nodeToRemove.NativeParent; if (nativeNodeToRemoveFrom != null) { var index = nativeNodeToRemoveFrom.GetIndexOfNativeChild(nodeToRemove); nativeNodeToRemoveFrom.RemoveNativeChildAt(index); _uiViewOperationQueue.EnqueueManageChildren( nativeNodeToRemoveFrom.ReactTag, new int[] { index }, null, shouldDelete ? new int[] { nodeToRemove.ReactTag } : null); } else { for (var i = nodeToRemove.ChildCount - 1; i >= 0; --i) { RemoveNodeFromParent(nodeToRemove.GetChildAt(i), shouldDelete); } } }
/// <summary> /// Handles a manage children call. This may translate into multiple /// manage children calls for multiple other views. /// </summary> /// <param name="nodeToManage">The node to manage.</param> /// <param name="indexesToRemove">The indices to remove.</param> /// <param name="tagsToRemove">The tags to remove.</param> /// <param name="viewsToAdd">The views to add.</param> /// <param name="tagsToDelete">The tags to delete.</param> /// <remarks> /// The assumption for calling this method is that all corresponding /// <see cref="ReactShadowNode"/>s have been updated, but /// <paramref name="tagsToDelete"/> have not been deleted yet. This is /// because we need to use the metadata from those nodes to figure out /// the correct commands to dispatch. This is unlike other calls on /// this class where we assume all operations on the shadow hierarchy /// have already completed by the time a corresponding method here is /// called. /// </remarks> public void HandleManageChildren(ReactShadowNode nodeToManage, int[] indexesToRemove, int[] tagsToRemove, ViewAtIndex[] viewsToAdd, int[] tagsToDelete) { #if !ENABLED _uiViewOperationQueue.EnqueueManageChildren( nodeToManage.ReactTag, indicesToRemove, viewsToAdd, tagsToDelete); #else // We operate on tagsToRemove instead of indicesToDelete because by // the time this method is called, these views have already been // removed from the shadow hierarchy and the indices are no longer // useful to operate on. for (var i = 0; i < tagsToRemove.Length; ++i) { var tagToRemove = tagsToRemove[i]; var delete = tagsToDelete.Contains(tagToRemove); var nodeToRemove = _shadowNodeRegistry.GetNode(tagToRemove); RemoveNodeFromParent(nodeToRemove, delete); } for (var i = 0; i < viewsToAdd.Length; ++i) { var toAdd = viewsToAdd[i]; var nodeToAdd = _shadowNodeRegistry.GetNode(toAdd.Tag); AddNodeToNode(nodeToManage, nodeToAdd, toAdd.Index); } #endif }
/// <summary> /// Handles a setChildren call. This is a simplification of <see cref="HandleManageChildren(ReactShadowNode, int[], int[], ViewAtIndex[], int[])"/> /// that only adds children in index order of the <paramref name="childrenTags"/> /// array. /// </summary> /// <param name="nodeToManage">The node to manage.</param> /// <param name="childrenTags">The children tags.</param> public void HandleSetChildren(ReactShadowNode nodeToManage, int[] childrenTags) { #if !ENABLED _uiViewOperationQueue.EnqueueSetChildren( nodeToManage.ReactTag, childrenTags); #else for (var i = 0; i < childrenTags.Length; ++i) { var nodeToAdd = _shadowNodeRegistry.GetNode(childrenTags[i]); AddNodeToNode(nodeToManage, nodeToAdd, i); } #endif }
private void ApplyUpdatesRecursive( ReactShadowNode cssNode, EventDispatcher eventDispatcher) { if (!cssNode.HasUpdates) { return; } if (!cssNode.IsVirtualAnchor) { for (var i = 0; i < cssNode.ChildCount; ++i) { ApplyUpdatesRecursive( cssNode.GetChildAt(i), eventDispatcher); } } var tag = cssNode.ReactTag; if (!_shadowNodeRegistry.IsRootNode(tag)) { cssNode.DispatchUpdates( _operationsQueue, _nativeViewHierarchyOptimizer); if (cssNode.ShouldNotifyOnLayout) { eventDispatcher.DispatchEvent( OnLayoutEvent.Obtain( tag, cssNode.LayoutX, cssNode.LayoutY, cssNode.LayoutWidth, cssNode.LayoutHeight)); } } cssNode.MarkUpdateSeen(); }
/// <summary> /// Handles native children cleanup when the shadow node is removed /// from the hierarchy. /// </summary> /// <param name="node">The node to cleanup.</param> public void HandleRemoveNode(ReactShadowNode node) { node.RemoveAllNativeChildren(); }
private void TransitionLayoutOnlyViewToNativeView(ReactShadowNode node, ReactStylesDiffMap props) { var parent = node.Parent; if (parent == null) { node.IsLayoutOnly = false; return; } // First, remove the node from its parent. This causes the parent // to update its native children count. The call will cause all the // view's children to be detached from their native parent. var childIndex = parent.IndexOf(node); parent.RemoveChildAt(childIndex); RemoveNodeFromParent(node, false); node.IsLayoutOnly = false; // Create the view since it doesn't exist in the native hierarchy yet. _uiViewOperationQueue.EnqueueCreateView( node.RootNode.ThemedContext, node.ReactTag, node.ViewClass, props); // Add the node and all its children as if adding new nodes. parent.AddChildAt(node, childIndex); AddNodeToNode(parent, node, childIndex); for (var i = 0; i < node.ChildCount; ++i) { AddNodeToNode(node, node.GetChildAt(i), i); } // Update layouts since the children of the node were offset by its // x/y position previously. (Warning: bit of a hack) We need to // update the layout of this node's children now that it's no // longer layout-only, but we may still receive more layout updates // at the end of this batch that we don't want to ignore. ApplyLayoutBase(node); for (var i = 0; i < node.ChildCount; ++i) { ApplyLayoutBase(node.GetChildAt(i)); } _tagsWithLayoutVisited.Clear(); }
private void ApplyLayoutRecursive(ReactShadowNode node, double x, double y) { if (!node.IsLayoutOnly && node.NativeParent != null) { _uiViewOperationQueue.EnqueueUpdateLayout( node.NativeParent.ReactTag, node.ReactTag, new Dimensions { X = x, Y = y, Width = node.LayoutWidth, Height = node.LayoutHeight, }); return; } for (var i = 0; i < node.ChildCount; ++i) { var child = node.GetChildAt(i); var visited = default(bool); if (_tagsWithLayoutVisited.TryGetValue(child.ReactTag, out visited) && visited) { continue; } _tagsWithLayoutVisited.Add(child.ReactTag, true); var childX = (double)child.LayoutX; var childY = (double)child.LayoutY; childX += x; childY += y; ApplyLayoutRecursive(child, childX, childY); } }
private void ApplyLayoutBase(ReactShadowNode node) { var tag = node.ReactTag; var visited = default(bool); if (_tagsWithLayoutVisited.TryGetValue(tag, out visited) && visited) { return; } _tagsWithLayoutVisited.Add(tag, true); var parent = node.Parent; // We use ScreenX/ScreenY (which round to integer pixels) at each // node in the hierarchy to emulate what the layout would look like // if it were actually built with native views, which have integral // top/left/bottom/right values. var x = node.LayoutX; var y = node.LayoutY; while (parent != null && parent.IsLayoutOnly) { x += parent.LayoutX; y += parent.LayoutY; parent = parent.Parent; } // This is a hack that accomodates for the fact that borders are // wrapped around the canvases that contain the UI elements. It is // likely to prove brittle over time, and we should consider either // alternate ways of drawing borders, or different mechanisms to // set absolute positions of elements. var borderParent = node.Parent; if (borderParent != null && borderParent.IsLayoutOnly) { borderParent = node.NativeParent; } if (borderParent != null) { x -= borderParent.GetLeftBorderWidth(); y -= borderParent.GetTopBorderWidth(); } ApplyLayoutRecursive(node, x, y); }
private void AddNonLayoutOnlyNodeToNonLayoutOnlyNode(ReactShadowNode parent, ReactShadowNode child, int index) { parent.AddNativeChildAt(child, index); _uiViewOperationQueue.EnqueueManageChildren( parent.ReactTag, null, new[] { new ViewAtIndex(child.ReactTag, index) }, null); }
private void AddLayoutOnlyNodeToNonLayoutOnlyNode(ReactShadowNode parent, ReactShadowNode child, int index) { var currentIndex = index; for (var i = 0; i < child.ChildCount; ++i) { var childToAdd = child.GetChildAt(i); if (childToAdd.IsLayoutOnly) { var childCountBefore = parent.NativeChildCount; AddLayoutOnlyNodeToNonLayoutOnlyNode( parent, childToAdd, currentIndex); var childCountAfter = parent.NativeChildCount; currentIndex += childCountAfter - childCountBefore; } else { AddNonLayoutOnlyNodeToNonLayoutOnlyNode(parent, childToAdd, currentIndex++); } } }
private void AddNonLayoutOnlyNodeToLayoutOnlyNode(ReactShadowNode parent, ReactShadowNode child, int index) { var parentParent = parent.Parent; // If the parent hasn't been attached to its parent yet, don't // issue commands to the native hierarchy. This will occur when the // parent node actually gets attached somewhere. if (parentParent == null) { return; } var transformedIndex = index + parentParent.GetNativeOffsetForChild(parent); if (parentParent.IsLayoutOnly) { AddNonLayoutOnlyNodeToLayoutOnlyNode(parentParent, child, transformedIndex); } else { AddNonLayoutOnlyNodeToNonLayoutOnlyNode(parentParent, child, transformedIndex); } }
private void NotifyBeforeOnLayoutRecursive(ReactShadowNode cssNode) { if (!cssNode.HasUpdates) { return; } for (var i = 0; i < cssNode.ChildCount; ++i) { NotifyBeforeOnLayoutRecursive(cssNode.GetChildAt(i)); } cssNode.OnBeforeLayout(); }
private void CalculateRootLayout(ReactShadowNode cssRoot) { using (Tracer.Trace(Tracer.TRACE_TAG_REACT_BRIDGE, "ReactShadowNode.CalculateLayout") .With("RootTag", cssRoot.ReactTag) .Start()) { cssRoot.CalculateLayout(); } }
/// <summary> /// Handles a call to <see cref="UIManagerModule.updateView(int, string, Newtonsoft.Json.Linq.JObject)"/>. /// If a view transitions from being layout-only to not (or vice versa) /// this could result in some number of additional create view or /// manage children calls. If the view is layout only, no update view /// call will be dispatched to the native hierarchy. /// </summary> /// <param name="node">The node.</param> /// <param name="className">The class name.</param> /// <param name="props">The properties.</param> public void HandleUpdateView(ReactShadowNode node, string className, ReactStylesDiffMap props) { #if !ENABLED _uiViewOperationQueue.EnqueueUpdateProperties(node.ReactTag, className, props); #else var needsToLeaveLayoutOnly = node.IsLayoutOnly && !IsLayoutOnlyAndCollapsible(props); if (needsToLeaveLayoutOnly) { TransitionLayoutOnlyViewToNativeView(node, props); } else if (!node.IsLayoutOnly) { _uiViewOperationQueue.EnqueueUpdateProperties(node.ReactTag, className, props); } #endif }
/// <summary> /// Handles an update layout call. All update layout calls are /// collected and dispatched at the end of a batch because update /// layout calls to layout-only nodes can necessitate multiple update /// layout calls for all its children. /// </summary> /// <param name="node">The node.</param> public void HandleUpdateLayout(ReactShadowNode node) { #if !ENABLED _uiViewOperationQueue.EnqueueUpdateLayout( node.Parent.ReactTag, node.ReactTag, new Dimensions { X = node.LayoutX, Y = node.LayoutY, Width = node.LayoutWidth, Height = node.LayoutHeight }); #else ApplyLayoutBase(node); #endif }
private void HandleCreateView(ReactShadowNode cssNode, int rootViewTag, ReactStylesDiffMap styles) { if (!cssNode.IsVirtual) { _nativeViewHierarchyOptimizer.HandleCreateView(cssNode, cssNode.ThemedContext, styles); } }
public static Inline Apply(ReactShadowNode node) { return(s_instance.Visit(node)); }
private void HandleUpdateView( ReactShadowNode cssNode, string className, ReactStylesDiffMap styles) { if (!cssNode.IsVirtual) { _nativeViewHierarchyOptimizer.HandleUpdateView(cssNode, className, styles); } }
private ReactShadowNode CreateRootShadowNode() { var rootCssNode = new ReactShadowNode(); if (I18NUtil.IsRightToLeft) { rootCssNode.Direction = CSSDirection.RTL; } rootCssNode.ViewClass = "Root"; return rootCssNode; }
private void AddNodeToNode(ReactShadowNode parent, ReactShadowNode child, int index) { var indexInNativeChildren = parent.GetNativeOffsetForChild(parent.GetChildAt(index)); if (!parent.IsLayoutOnly && !child.IsLayoutOnly) { AddNonLayoutOnlyNodeToNonLayoutOnlyNode(parent, child, indexInNativeChildren); } else if (!child.IsLayoutOnly) { AddNonLayoutOnlyNodeToLayoutOnlyNode(parent, child, indexInNativeChildren); } else if (!parent.IsLayoutOnly) { AddLayoutOnlyNodeToNonLayoutOnlyNode(parent, child, indexInNativeChildren); } else { AddLayoutOnlyNodeToLayoutOnlyNode(parent, child, indexInNativeChildren); } }