From 5c2f536e9a914f9d60915d3dae69fefd001027b7 Mon Sep 17 00:00:00 2001 From: Denis Koroskin Date: Sun, 6 Dec 2015 16:00:36 -0800 Subject: [PATCH] Add support for RCTText under FlatUIImplementation Summary: @public Initial version of FlatUIImplementation lacks any primitives support (such as RCTText, RCTImageView or RCTView). This diff add the first part, RCTText (alongside with RCTVirtualText and RCTRawText). Reviewed By: sriramramani Differential Revision: D2693348 --- .../react/flat/AbstractDrawCommand.java | 108 +++++++++ .../com/facebook/react/flat/DrawCommand.java | 29 +++ .../facebook/react/flat/DrawTextLayout.java | 42 ++++ .../flat/FlatNativeViewHierarchyManager.java | 77 +++++++ .../react/flat/FlatRootShadowNode.java | 96 ++++++++ .../facebook/react/flat/FlatShadowNode.java | 31 +++ .../react/flat/FlatTextShadowNode.java | 47 ++++ .../react/flat/FlatUIImplementation.java | 48 ++-- .../react/flat/FlatUIViewOperationQueue.java | 88 ++++++++ .../facebook/react/flat/FlatViewGroup.java | 46 ++++ .../facebook/react/flat/FontStylingSpan.java | 99 +++++++++ .../com/facebook/react/flat/RCTRawText.java | 44 ++++ .../react/flat/RCTRawTextManager.java | 31 +++ .../java/com/facebook/react/flat/RCTText.java | 209 ++++++++++++++++++ .../facebook/react/flat/RCTTextManager.java | 31 +++ .../java/com/facebook/react/flat/RCTView.java | 16 ++ .../facebook/react/flat/RCTViewManager.java | 31 +++ .../facebook/react/flat/RCTVirtualText.java | 174 +++++++++++++++ .../react/flat/RCTVirtualTextManager.java | 31 +++ .../com/facebook/react/flat/StateBuilder.java | 179 +++++++++++++++ .../react/flat/VirtualViewManager.java | 29 +++ 21 files changed, 1466 insertions(+), 20 deletions(-) create mode 100644 ReactAndroid/src/main/java/com/facebook/react/flat/AbstractDrawCommand.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/flat/DrawCommand.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/flat/DrawTextLayout.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/flat/FlatNativeViewHierarchyManager.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/flat/FlatRootShadowNode.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/flat/FlatShadowNode.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/flat/FlatTextShadowNode.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/flat/FlatUIViewOperationQueue.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/flat/FlatViewGroup.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/flat/FontStylingSpan.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/flat/RCTRawText.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/flat/RCTRawTextManager.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/flat/RCTText.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/flat/RCTTextManager.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/flat/RCTView.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/flat/RCTViewManager.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/flat/RCTVirtualText.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/flat/RCTVirtualTextManager.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/flat/StateBuilder.java create mode 100644 ReactAndroid/src/main/java/com/facebook/react/flat/VirtualViewManager.java diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/AbstractDrawCommand.java b/ReactAndroid/src/main/java/com/facebook/react/flat/AbstractDrawCommand.java new file mode 100644 index 000000000..37df9f2d8 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/AbstractDrawCommand.java @@ -0,0 +1,108 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.flat; + +/** + * Base class for all DrawCommands. Becomes immutable once it has its bounds set. Until then, a + * subclass is able to mutate any of its properties (e.g. updating Layout in DrawTextLayout). + * + * The idea is to be able to reuse unmodified objects when we build up DrawCommands before we ship + * them to UI thread, but we can only do that if DrawCommands are immutable. + */ +/* package */ abstract class AbstractDrawCommand implements DrawCommand, Cloneable { + + private float mLeft; + private float mTop; + private float mRight; + private float mBottom; + private boolean mFrozen; + + /** + * Updates boundaries of the AbstractDrawCommand and freezes it. + * Will return a frozen copy if the current AbstractDrawCommand cannot be mutated. + */ + public final AbstractDrawCommand updateBoundsAndFreeze( + float left, + float top, + float right, + float bottom) { + if (mFrozen) { + // see if we can reuse it + if (boundsMatch(left, top, right, bottom)) { + return this; + } + + try { + AbstractDrawCommand copy = (AbstractDrawCommand) clone(); + copy.setBounds(left, top, right, bottom); + return copy; + } catch (CloneNotSupportedException e) { + // This should not happen since AbstractDrawCommand implements Cloneable + throw new RuntimeException(e); + } + } + + setBounds(left, top, right, bottom); + mFrozen = true; + return this; + } + + /** + * Returns whether this object was frozen and thus cannot be mutated. + */ + public final boolean isFrozen() { + return mFrozen; + } + + /** + * Left position of this DrawCommand relative to the hosting View. + */ + public final float getLeft() { + return mLeft; + } + + /** + * Top position of this DrawCommand relative to the hosting View. + */ + public final float getTop() { + return mTop; + } + + /** + * Right position of this DrawCommand relative to the hosting View. + */ + public final float getRight() { + return mRight; + } + + /** + * Bottom position of this DrawCommand relative to the hosting View. + */ + public final float getBottom() { + return mBottom; + } + + /** + * Updates boundaries of this DrawCommand. + */ + private void setBounds(float left, float top, float right, float bottom) { + mLeft = left; + mTop = top; + mRight = right; + mBottom = bottom; + } + + /** + * Returns true if boundaries match and don't need to be updated. False otherwise. + */ + private boolean boundsMatch(float left, float top, float right, float bottom) { + return mLeft == left && mTop == top && mRight == right && mBottom == bottom; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/DrawCommand.java b/ReactAndroid/src/main/java/com/facebook/react/flat/DrawCommand.java new file mode 100644 index 000000000..7caceabca --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/DrawCommand.java @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.flat; + +import android.graphics.Canvas; + +/** + * DrawCommand is an inteface that shadow nodes need to implement to do the drawing. + * Instaces of DrawCommand are created in background thread and passed to UI thread. + * Once a DrawCommand is shared with UI thread, it can no longer be mutated in background thread. + */ +public interface DrawCommand { + // used by StateBuilder, FlatViewGroup and FlatShadowNode + /* package */ static final DrawCommand[] EMPTY_ARRAY = new DrawCommand[0]; + + /** + * Performs drawing into the given canvas. + * + * @param canvas The canvas to draw into + */ + public void draw(Canvas canvas); +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/DrawTextLayout.java b/ReactAndroid/src/main/java/com/facebook/react/flat/DrawTextLayout.java new file mode 100644 index 000000000..abccbbe56 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/DrawTextLayout.java @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.flat; + +import android.graphics.Canvas; +import android.text.Layout; + +/** + * DrawTextLayout is a DrawCommand that draw {@link Layout}. + */ +/* package */ final class DrawTextLayout extends AbstractDrawCommand { + + private Layout mLayout; + + /* package */ DrawTextLayout(Layout layout) { + mLayout = layout; + } + + /** + * Assigns a new {@link Layout} to draw. + */ + public void setLayout(Layout layout) { + mLayout = layout; + } + + @Override + public void draw(Canvas canvas) { + float left = getLeft(); + float top = getTop(); + + canvas.translate(left, top); + mLayout.draw(canvas); + canvas.translate(-left, -top); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/FlatNativeViewHierarchyManager.java b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatNativeViewHierarchyManager.java new file mode 100644 index 000000000..4c75e766f --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatNativeViewHierarchyManager.java @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.flat; + +import android.view.View; +import android.view.View.MeasureSpec; + +import com.facebook.react.uimanager.NativeViewHierarchyManager; +import com.facebook.react.uimanager.SizeMonitoringFrameLayout; +import com.facebook.react.uimanager.ThemedReactContext; +import com.facebook.react.uimanager.ViewManagerRegistry; + +/** + * FlatNativeViewHierarchyManager is the only class that performs View manipulations. All of this + * class methods can only be called from UI thread by {@link FlatUIViewOperationQueue}. + */ +/* package */ final class FlatNativeViewHierarchyManager extends NativeViewHierarchyManager { + + /* package */ FlatNativeViewHierarchyManager(ViewManagerRegistry viewManagers) { + super(viewManagers); + } + + @Override + public void addRootView( + int tag, + SizeMonitoringFrameLayout view, + ThemedReactContext themedContext) { + FlatViewGroup root = new FlatViewGroup(themedContext); + view.addView(root); + + addRootViewGroup(tag, root, themedContext); + } + + /** + * Assigns new DrawCommands to a FlatViewGroup specified by a reactTag. + * + * @param reactTag reactTag to lookup FlatViewGroup by + * @param drawCommands new draw commands to execute during the drawing. + */ + /* package */ void updateMountState(int reactTag, DrawCommand[] drawCommands) { + FlatViewGroup view = (FlatViewGroup) resolveView(reactTag); + view.mountDrawCommands(drawCommands); + } + + /** + * Updates View bounds, possibly re-measuring and re-layouting it if the size changed. + * + * @param reactTag reactTag to lookup a View by + * @param left left coordinate relative to parent + * @param top top coordinate relative to parent + * @param right right coordinate relative to parent + * @param bottom bottom coordinate relative to parent + */ + /* package */ void updateViewBounds(int reactTag, int left, int top, int right, int bottom) { + View view = resolveView(reactTag); + int width = right - left; + int height = bottom - top; + if (view.getWidth() != width || view.getHeight() != height) { + // size changed, we need to measure and layout the View + view.measure( + MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); + view.layout(left, top, right, bottom); + } else { + // same size, only location changed, there is a faster route. + view.offsetLeftAndRight(left - view.getLeft()); + view.offsetTopAndBottom(top - view.getTop()); + } + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/FlatRootShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatRootShadowNode.java new file mode 100644 index 000000000..2bb93d52d --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatRootShadowNode.java @@ -0,0 +1,96 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.flat; + +/** + * Root node of the shadow node hierarchy. Currently, the only node that can actually map to a View. + */ +/* package */ final class FlatRootShadowNode extends FlatShadowNode { + + private DrawCommand[] mDrawCommands = DrawCommand.EMPTY_ARRAY; + + private int mViewLeft; + private int mViewTop; + private int mViewRight; + private int mViewBottom; + + @Override + public int getScreenX() { + return mViewLeft; + } + + @Override + public int getScreenY() { + return mViewTop; + } + + @Override + public int getScreenWidth() { + return mViewRight - mViewLeft; + } + + @Override + public int getScreenHeight() { + return mViewBottom - mViewTop; + } + + /** + * Returns an array of DrawCommands to perform during the View's draw pass. + */ + /* package */ DrawCommand[] getDrawCommands() { + return mDrawCommands; + } + + /** + * Sets an array of DrawCommands to perform during the View's draw pass. StateBuilder uses old + * draw commands to compare to new draw commands and see if the View neds to be redrawn. + */ + /* package */ void setDrawCommands(DrawCommand[] drawCommands) { + mDrawCommands = drawCommands; + } + + /** + * Sets boundaries of the View that this node maps to relative to the parent left/top coordinate. + */ + /* package */ void setViewBounds(int left, int top, int right, int bottom) { + mViewLeft = left; + mViewTop = top; + mViewRight = right; + mViewBottom = bottom; + } + + /** + * Left position of the View this node maps to relative to the parent View. + */ + /* package */ int getViewLeft() { + return mViewLeft; + } + + /** + * Top position of the View this node maps to relative to the parent View. + */ + /* package */ int getViewTop() { + return mViewTop; + } + + /** + * Right position of the View this node maps to relative to the parent View. + */ + /* package */ int getViewRight() { + return mViewRight; + } + + /** + * Bottom position of the View this node maps to relative to the parent View. + */ + /* package */ int getViewBottom() { + return mViewBottom; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/FlatShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatShadowNode.java new file mode 100644 index 000000000..475533b48 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatShadowNode.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.flat; + +import com.facebook.react.uimanager.LayoutShadowNode; + +/** + * FlatShadowNode is a base class for all shadow node used in FlatUIImplementation. It extends + * {@link LayoutShadowNode} by adding an ability to prepare DrawCommands off the UI thread. + */ +/* package */ class FlatShadowNode extends LayoutShadowNode { + + /** + * Collects DrawCommands produced by this FlatShadoNode. + */ + protected void collectState( + StateBuilder stateBuilder, + float left, + float top, + float right, + float bottom) { + // do nothing yet. + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/FlatTextShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatTextShadowNode.java new file mode 100644 index 000000000..bf0bfa795 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatTextShadowNode.java @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.flat; + +import android.text.SpannableStringBuilder; + +import com.facebook.react.uimanager.ReactShadowNode; + +/** + * Base class for RCTVirtualText and RCTRawText. + */ +/* package */ abstract class FlatTextShadowNode extends FlatShadowNode { + + /** + * Recursively visits FlatTextShadowNode and its children, + * appending text to SpannableStringBuilder. + */ + protected abstract void collectText(SpannableStringBuilder builder); + + /** + * Recursively visits FlatTextShadowNode and its children, + * applying spans to SpannableStringBuilder. + */ + protected abstract void applySpans(SpannableStringBuilder builder); + + /** + * Propagates changes up to RCTText without dirtying current node. + */ + protected void notifyChanged(boolean shouldRemeasure) { + ReactShadowNode parent = getParent(); + if (parent instanceof FlatTextShadowNode) { + ((FlatTextShadowNode) parent).notifyChanged(shouldRemeasure); + } + } + + @Override + public boolean isVirtual() { + return true; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/FlatUIImplementation.java b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatUIImplementation.java index e9d59f1a5..c17de9be7 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/FlatUIImplementation.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatUIImplementation.java @@ -9,6 +9,7 @@ package com.facebook.react.flat; +import java.util.ArrayList; import java.util.List; import javax.annotation.Nullable; @@ -33,20 +34,37 @@ import com.facebook.react.uimanager.events.EventDispatcher; */ public class FlatUIImplementation extends UIImplementation { - public FlatUIImplementation( + private final StateBuilder mStateBuilder; + + public static FlatUIImplementation createInstance( ReactApplicationContext reactContext, List viewManagers) { - this(reactContext, new ViewManagerRegistry(viewManagers)); + + viewManagers = new ArrayList(viewManagers); + viewManagers.add(new RCTViewManager()); + viewManagers.add(new RCTTextManager()); + viewManagers.add(new RCTRawTextManager()); + viewManagers.add(new RCTVirtualTextManager()); + + ViewManagerRegistry viewManagerRegistry = new ViewManagerRegistry(viewManagers); + FlatNativeViewHierarchyManager nativeViewHierarchyManager = new FlatNativeViewHierarchyManager( + viewManagerRegistry); + FlatUIViewOperationQueue operationsQueue = new FlatUIViewOperationQueue( + reactContext, + nativeViewHierarchyManager); + return new FlatUIImplementation(viewManagerRegistry, operationsQueue); } private FlatUIImplementation( - ReactApplicationContext reactContext, - ViewManagerRegistry viewManagers) { - super( - viewManagers, - new UIViewOperationQueue( - reactContext, - new NativeViewHierarchyManager(viewManagers))); + ViewManagerRegistry viewManagers, + FlatUIViewOperationQueue operationsQueue) { + super(viewManagers, operationsQueue); + mStateBuilder = new StateBuilder(operationsQueue); + } + + @Override + protected ReactShadowNode createRootShadowNode() { + return new FlatRootShadowNode(); } @Override @@ -122,16 +140,6 @@ public class FlatUIImplementation extends UIImplementation { float absoluteX, float absoluteY, EventDispatcher eventDispatcher) { - markNodeLayoutSeen(cssNode); - } - - private void markNodeLayoutSeen(CSSNode node) { - if (node.hasNewLayout()) { - node.markLayoutSeen(); - } - - for (int i = 0, childCount = node.getChildCount(); i != childCount; ++i) { - markNodeLayoutSeen(node.getChildAt(i)); - } + mStateBuilder.applyUpdates((FlatRootShadowNode) cssNode); } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/FlatUIViewOperationQueue.java b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatUIViewOperationQueue.java new file mode 100644 index 000000000..199bc0d6c --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatUIViewOperationQueue.java @@ -0,0 +1,88 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.flat; + +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.uimanager.UIViewOperationQueue; + +/** + * FlatUIViewOperationQueue extends {@link UIViewOperationQueue} to add + * FlatUIImplementation-specific methods that need to run in UI thread. + */ +/* package */ final class FlatUIViewOperationQueue extends UIViewOperationQueue { + + private final FlatNativeViewHierarchyManager mNativeViewHierarchyManager; + + /** + * UIOperation that updates DrawCommands for a View defined by reactTag. + */ + private final class UpdateMountState implements UIOperation { + + private final int mReactTag; + private final DrawCommand[] mDrawCommands; + + private UpdateMountState(int reactTag, DrawCommand[] drawCommands) { + mReactTag = reactTag; + mDrawCommands = drawCommands; + } + + @Override + public void execute() { + mNativeViewHierarchyManager.updateMountState(mReactTag, mDrawCommands); + } + } + + /** + * UIOperation that updates View bounds for a View defined by reactTag. + */ + private final class UpdateViewBounds implements UIOperation { + + private final int mReactTag; + private final int mLeft; + private final int mTop; + private final int mRight; + private final int mBottom; + + private UpdateViewBounds(int reactTag, int left, int top, int right, int bottom) { + mReactTag = reactTag; + mLeft = left; + mTop = top; + mRight = right; + mBottom = bottom; + } + + @Override + public void execute() { + mNativeViewHierarchyManager.updateViewBounds(mReactTag, mLeft, mTop, mRight, mBottom); + } + } + + public FlatUIViewOperationQueue( + ReactApplicationContext reactContext, + FlatNativeViewHierarchyManager nativeViewHierarchyManager) { + super(reactContext, nativeViewHierarchyManager); + + mNativeViewHierarchyManager = nativeViewHierarchyManager; + } + + /** + * Enqueues a new UIOperation that will update DrawCommands for a View defined by reactTag. + */ + public void enqueueUpdateMountState(int reactTag, DrawCommand[] drawCommands) { + enqueueUIOperation(new UpdateMountState(reactTag, drawCommands)); + } + + /** + * Enqueues a new UIOperation that will update View bounds for a View defined by reactTag. + */ + public void enqueueUpdateViewBounds(int reactTag, int left, int top, int right, int bottom) { + enqueueUIOperation(new UpdateViewBounds(reactTag, left, top, right, bottom)); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/FlatViewGroup.java b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatViewGroup.java new file mode 100644 index 000000000..ab6112892 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatViewGroup.java @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.flat; + +import android.content.Context; +import android.graphics.Canvas; +import android.view.ViewGroup; + +/** + * A view that FlatShadowNode hierarchy maps to. Performs drawing by iterating over + * array of DrawCommands, executing them one by one. + */ +/* package */ final class FlatViewGroup extends ViewGroup { + + private DrawCommand[] mDrawCommands = DrawCommand.EMPTY_ARRAY; + + /* package */ FlatViewGroup(Context context) { + super(context); + } + + @Override + public void dispatchDraw(Canvas canvas) { + super.dispatchDraw(canvas); + + for (DrawCommand drawCommand : mDrawCommands) { + drawCommand.draw(canvas); + } + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + // nothing to do here + } + + /* package */ void mountDrawCommands(DrawCommand[] drawCommands) { + mDrawCommands = drawCommands; + invalidate(); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/FontStylingSpan.java b/ReactAndroid/src/main/java/com/facebook/react/flat/FontStylingSpan.java new file mode 100644 index 000000000..b5fd96166 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/FontStylingSpan.java @@ -0,0 +1,99 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.flat; + +import javax.annotation.Nullable; + +import android.graphics.Typeface; +import android.text.TextPaint; +import android.text.style.MetricAffectingSpan; + +/* package */ final class FontStylingSpan extends MetricAffectingSpan { + // text property + private final double mTextColor; + private final int mBackgroundColor; + + // font properties + private final int mFontSize; + private final int mFontStyle; + private final int mFontWeight; + private final @Nullable String mFontFamily; + + FontStylingSpan( + double textColor, + int backgroundColor, + int fontSize, + int fontStyle, + int fontWeight, + @Nullable String fontFamily) { + mTextColor = textColor; + mBackgroundColor = backgroundColor; + mFontSize = fontSize; + mFontStyle = fontStyle; + mFontWeight = fontWeight; + mFontFamily = fontFamily; + } + + @Override + public void updateDrawState(TextPaint ds) { + if (!Double.isNaN(mTextColor)) { + ds.setColor((int) mTextColor); + } + + ds.bgColor = mBackgroundColor; + updateMeasureState(ds); + } + + @Override + public void updateMeasureState(TextPaint ds) { + if (mFontSize != -1) { + ds.setTextSize(mFontSize); + } + + updateTypeface(ds); + } + + private int getNewStyle(int oldStyle) { + int newStyle = oldStyle; + if (mFontStyle != -1) { + newStyle = (newStyle & ~Typeface.ITALIC) | mFontStyle; + } + + if (mFontWeight != -1) { + newStyle = (newStyle & ~Typeface.BOLD) | mFontWeight; + } + + return newStyle; + } + + private void updateTypeface(TextPaint ds) { + Typeface typeface = ds.getTypeface(); + + int oldStyle = (typeface == null) ? 0 : typeface.getStyle(); + int newStyle = getNewStyle(oldStyle); + + if (oldStyle == newStyle && mFontFamily == null) { + // nothing to do + return; + } + + // TODO: optimize this part (implemented in a followup patch) + + if (mFontFamily != null) { + // efficient in API 21+ + typeface = Typeface.create(mFontFamily, newStyle); + } else { + // efficient in API 16+ + typeface = Typeface.create(typeface, newStyle); + } + + ds.setTypeface(typeface); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/RCTRawText.java b/ReactAndroid/src/main/java/com/facebook/react/flat/RCTRawText.java new file mode 100644 index 000000000..18bd28ced --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/RCTRawText.java @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.flat; + +import javax.annotation.Nullable; + +import android.text.SpannableStringBuilder; + +import com.facebook.react.uimanager.ReactProp; + +/** + * RCTRawText is a FlatTextShadowNode that can only contain raw text (but not styling). + */ +/* package */ class RCTRawText extends FlatTextShadowNode { + + private @Nullable String mText; + + @Override + protected void collectText(SpannableStringBuilder builder) { + if (mText != null) { + builder.append(mText); + } + + // RCTRawText cannot have any children, so no recursive calls needed. + } + + @Override + protected void applySpans(SpannableStringBuilder builder) { + // no spans and no children so nothing to do here. + } + + @ReactProp(name = "text") + public void setText(@Nullable String text) { + mText = text; + notifyChanged(true); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/RCTRawTextManager.java b/ReactAndroid/src/main/java/com/facebook/react/flat/RCTRawTextManager.java new file mode 100644 index 000000000..ee2e557f4 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/RCTRawTextManager.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.flat; + +/** + * ViewManager that creates instances of RCTRawText. + */ +/* package */ final class RCTRawTextManager extends VirtualViewManager { + + @Override + public String getName() { + return "RCTRawText"; + } + + @Override + public RCTRawText createShadowNodeInstance() { + return new RCTRawText(); + } + + @Override + public Class getShadowNodeClass() { + return RCTRawText.class; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/RCTText.java b/ReactAndroid/src/main/java/com/facebook/react/flat/RCTText.java new file mode 100644 index 000000000..5920e09f4 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/RCTText.java @@ -0,0 +1,209 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.flat; + +import javax.annotation.Nullable; + +import android.text.BoringLayout; +import android.text.Layout; +import android.text.SpannableStringBuilder; +import android.text.StaticLayout; +import android.text.TextPaint; +import android.text.TextUtils; + +import com.facebook.csslayout.CSSNode; +import com.facebook.csslayout.MeasureOutput; +import com.facebook.react.uimanager.PixelUtil; +import com.facebook.react.uimanager.ReactProp; +import com.facebook.react.uimanager.ViewDefaults; +import com.facebook.react.uimanager.ViewProps; + +/** + * RCTText is a top-level node for text. It extends {@link RCTVirtualText} because it can contain + * styling information, but has the following differences: + * + * a) RCTText is not a virtual node, and can be measured and laid out. + * b) when no font size is specified, a font size of ViewDefaults#FONT_SIZE_SP is assumed. + */ +/* package */ final class RCTText extends RCTVirtualText implements CSSNode.MeasureFunction { + + private static final boolean INCLUDE_PADDING = true; + private static final TextPaint PAINT = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); + + // this is optional, and helps saving a few BoringLayout.Metrics allocations during measure(). + private static @Nullable BoringLayout.Metrics sBoringLayoutMetrics; + + private @Nullable CharSequence mText; + private @Nullable DrawTextLayout mDrawCommand; + private @Nullable BoringLayout.Metrics mBoringLayoutMetrics; + private float mSpacingMult = 1.0f; + private float mSpacingAdd = 0.0f; + + public RCTText() { + setMeasureFunction(this); + } + + @Override + public boolean isVirtual() { + return false; + } + + @Override + public boolean isVirtualAnchor() { + return true; + } + + @Override + public void measure(CSSNode node, float width, MeasureOutput measureOutput) { + CharSequence text = getText(); + + if (TextUtils.isEmpty(text)) { + // to indicate that we don't have anything to display + mText = null; + measureOutput.width = 0; + measureOutput.height = 0; + return; + } + + mText = text; + + BoringLayout.Metrics metrics = BoringLayout.isBoring(text, PAINT, sBoringLayoutMetrics); + if (metrics != null) { + sBoringLayoutMetrics = mBoringLayoutMetrics; + if (sBoringLayoutMetrics != null) { + // make sure it's always empty, reported metrics can be incorrect otherwise + sBoringLayoutMetrics.top = 0; + sBoringLayoutMetrics.ascent = 0; + sBoringLayoutMetrics.descent = 0; + sBoringLayoutMetrics.bottom = 0; + sBoringLayoutMetrics.leading = 0; + } + + mBoringLayoutMetrics = metrics; + + float measuredWidth = (float) metrics.width; + if (Float.isNaN(width) || measuredWidth <= width) { + measureOutput.width = measuredWidth; + measureOutput.height = getMetricsHeight(metrics, INCLUDE_PADDING); + + // to indicate that text layout was not created during the measure pass + mDrawCommand = null; + + return; + } + + // width < measuredWidth -> more that a single line -> not boring + } + + int maximumWidth = Float.isNaN(width) ? Integer.MAX_VALUE : (int) width; + + // at this point we need to create a StaticLayout to measure the text + StaticLayout layout = new StaticLayout( + text, + PAINT, + maximumWidth, + Layout.Alignment.ALIGN_NORMAL, + mSpacingMult, + mSpacingAdd, + INCLUDE_PADDING); + + // determine how wide we actually are + float maxLineWidth = 0; + int lineCount = layout.getLineCount(); + for (int i = 0; i != lineCount; ++i) { + maxLineWidth = Math.max(maxLineWidth, layout.getLineMax(i)); + } + + measureOutput.width = maxLineWidth; + measureOutput.height = layout.getHeight(); + + if (mDrawCommand != null && !mDrawCommand.isFrozen()) { + mDrawCommand.setLayout(layout); + } else { + mDrawCommand = new DrawTextLayout(layout); + } + } + + @Override + protected void collectState( + StateBuilder stateBuilder, + float left, + float top, + float right, + float bottom) { + super.collectState(stateBuilder, left, top, right, bottom); + + if (mText == null) { + // nothing to draw (empty text). + return; + } + + if (mDrawCommand == null) { + // Layout was not created during the measure pass, must be Boring, create it now + mDrawCommand = new DrawTextLayout(new BoringLayout( + mText, + PAINT, + Integer.MAX_VALUE, // fits one line so don't care about the width + Layout.Alignment.ALIGN_NORMAL, + mSpacingMult, + mSpacingAdd, + mBoringLayoutMetrics, + INCLUDE_PADDING)); + } + + mDrawCommand = (DrawTextLayout) mDrawCommand.updateBoundsAndFreeze(left, top, right, bottom); + stateBuilder.addDrawCommand(mDrawCommand); + } + + @ReactProp(name = ViewProps.LINE_HEIGHT, defaultDouble = Double.NaN) + public void setLineHeight(double lineHeight) { + if (Double.isNaN(lineHeight)) { + mSpacingMult = 1.0f; + mSpacingAdd = 0.0f; + } else { + mSpacingMult = 0.0f; + mSpacingAdd = PixelUtil.toPixelFromSP((float) lineHeight); + } + notifyChanged(true); + } + + @Override + protected int getDefaultFontSize() { + // top-level should always specify font size. + return fontSizeFromSp(ViewDefaults.FONT_SIZE_SP); + } + + @Override + protected void notifyChanged(boolean shouldRemeasure) { + // Future patch: should only recreate Layout if shouldRemeasure is false + dirty(); + } + + /** + * Returns a new CharSequence that includes all the text and styling information to create Layout. + */ + private CharSequence getText() { + SpannableStringBuilder sb = new SpannableStringBuilder(); + collectText(sb); + applySpans(sb); + return sb; + } + + /** + * Returns measured line height according to an includePadding flag. + */ + private static int getMetricsHeight(BoringLayout.Metrics metrics, boolean includePadding) { + if (includePadding) { + return metrics.bottom - metrics.top; + } else { + return metrics.descent - metrics.ascent; + } + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/RCTTextManager.java b/ReactAndroid/src/main/java/com/facebook/react/flat/RCTTextManager.java new file mode 100644 index 000000000..751a9214f --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/RCTTextManager.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.flat; + +/** + * ViewManager that creates instances of RCTText. + */ +/* package */ final class RCTTextManager extends VirtualViewManager { + + @Override + public String getName() { + return "RCTText"; + } + + @Override + public RCTText createShadowNodeInstance() { + return new RCTText(); + } + + @Override + public Class getShadowNodeClass() { + return RCTText.class; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/RCTView.java b/ReactAndroid/src/main/java/com/facebook/react/flat/RCTView.java new file mode 100644 index 000000000..642c63b9a --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/RCTView.java @@ -0,0 +1,16 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.flat; + +/** + * Dummy implementation of RCTView. + */ +/* package */ final class RCTView extends FlatShadowNode { +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/RCTViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/flat/RCTViewManager.java new file mode 100644 index 000000000..aa860b616 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/RCTViewManager.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.flat; + +/** + * ViewManager that creates instances of RCTView. + */ +/* package */ final class RCTViewManager extends VirtualViewManager { + + @Override + public String getName() { + return "RCTView"; + } + + @Override + public RCTView createShadowNodeInstance() { + return new RCTView(); + } + + @Override + public Class getShadowNodeClass() { + return RCTView.class; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/RCTVirtualText.java b/ReactAndroid/src/main/java/com/facebook/react/flat/RCTVirtualText.java new file mode 100644 index 000000000..5189947b5 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/RCTVirtualText.java @@ -0,0 +1,174 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.flat; + +import javax.annotation.Nullable; + +import android.graphics.Typeface; +import android.text.Spannable; +import android.text.SpannableStringBuilder; + +import com.facebook.react.uimanager.PixelUtil; +import com.facebook.react.uimanager.ReactProp; +import com.facebook.react.uimanager.ViewProps; + +/** + * RCTVirtualText is a {@link FlatTextShadowNode} that can contain font styling information. + */ +/* package */ class RCTVirtualText extends FlatTextShadowNode { + + private static final String BOLD = "bold"; + private static final String ITALIC = "italic"; + private static final String NORMAL = "normal"; + + // TODO: cache CustomStyleSpan and move remove these values from here + // (implemented in a followup patch) + private double mTextColor = Double.NaN; + private int mBgColor; + private int mFontSize = getDefaultFontSize(); + private int mFontStyle = -1; // -1, Typeface.NORMAL or Typeface.ITALIC + private int mFontWeight = -1; // -1, Typeface.NORMAL or Typeface.BOLD + private @Nullable String mFontFamily; + + // these 2 are only used between collectText() and applySpans() calls. + private int mTextBegin; + private int mTextEnd; + + @Override + protected void collectText(SpannableStringBuilder builder) { + int childCount = getChildCount(); + + mTextBegin = builder.length(); + for (int i = 0; i < childCount; ++i) { + FlatTextShadowNode child = (FlatTextShadowNode) getChildAt(i); + child.collectText(builder); + } + mTextEnd = builder.length(); + } + + @Override + protected void applySpans(SpannableStringBuilder builder) { + if (mTextBegin == mTextEnd) { + return; + } + + builder.setSpan( + // Future patch: cache last custom style span with a frozen flag + new FontStylingSpan(mTextColor, mBgColor, mFontSize, mFontStyle, mFontWeight, mFontFamily), + mTextBegin, + mTextEnd, + Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + + int childCount = getChildCount(); + + for (int i = 0; i < childCount; ++i) { + FlatTextShadowNode child = (FlatTextShadowNode) getChildAt(i); + child.applySpans(builder); + } + } + + @ReactProp(name = ViewProps.FONT_SIZE, defaultFloat = Float.NaN) + public void setFontSize(float fontSizeSp) { + final int fontSize; + if (Float.isNaN(fontSizeSp)) { + fontSize = getDefaultFontSize(); + } else { + fontSize = fontSizeFromSp(fontSizeSp); + } + + if (mFontSize != fontSize) { + mFontSize = fontSize; + notifyChanged(true); + } + } + + @ReactProp(name = ViewProps.COLOR, defaultDouble = Double.NaN) + public void setColor(double textColor) { + if (mTextColor != textColor) { + mTextColor = textColor; + notifyChanged(false); + } + } + + @ReactProp(name = ViewProps.BACKGROUND_COLOR) + public void setBackgroundColor(int backgroundColor) { + if (mBgColor != backgroundColor) { + mBgColor = backgroundColor; + notifyChanged(false); + } + } + + @ReactProp(name = ViewProps.FONT_FAMILY) + public void setFontFamily(@Nullable String fontFamily) { + mFontFamily = fontFamily; + notifyChanged(true); + } + + @ReactProp(name = ViewProps.FONT_WEIGHT) + public void setFontWeight(@Nullable String fontWeightString) { + final int fontWeight; + if (fontWeightString == null) { + fontWeight = -1; + } else if (BOLD.equals(fontWeightString)) { + fontWeight = Typeface.BOLD; + } else if (NORMAL.equals(fontWeightString)) { + fontWeight = Typeface.NORMAL; + } else { + int fontWeightNumeric = parseNumericFontWeight(fontWeightString); + if (fontWeightNumeric == -1) { + throw new RuntimeException("invalid font weight " + fontWeightString); + } + fontWeight = fontWeightNumeric >= 500 ? Typeface.BOLD : Typeface.NORMAL; + } + + if (mFontWeight != fontWeight) { + mFontWeight = fontWeight; + notifyChanged(true); + } + } + + @ReactProp(name = ViewProps.FONT_STYLE) + public void setFontStyle(@Nullable String fontStyleString) { + final int fontStyle; + if (fontStyleString == null) { + fontStyle = -1; + } else if (ITALIC.equals(fontStyleString)) { + fontStyle = Typeface.ITALIC; + } else if (NORMAL.equals(fontStyleString)) { + fontStyle = Typeface.NORMAL; + } else { + throw new RuntimeException("invalid font style " + fontStyleString); + } + + if (mFontStyle != fontStyle) { + mFontStyle = fontStyle; + notifyChanged(true); + } + } + + protected int getDefaultFontSize() { + return -1; + } + + /* package */ static int fontSizeFromSp(float sp) { + return (int) Math.ceil(PixelUtil.toPixelFromSP(sp)); + } + + /** + * Return -1 if the input string is not a valid numeric fontWeight (100, 200, ..., 900), otherwise + * return the weight. + */ + private static int parseNumericFontWeight(String fontWeightString) { + // This should be much faster than using regex to verify input and Integer.parseInt + return fontWeightString.length() == 3 && fontWeightString.endsWith("00") + && fontWeightString.charAt(0) <= '9' && fontWeightString.charAt(0) >= '1' ? + 100 * (fontWeightString.charAt(0) - '0') : -1; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/RCTVirtualTextManager.java b/ReactAndroid/src/main/java/com/facebook/react/flat/RCTVirtualTextManager.java new file mode 100644 index 000000000..155ee9682 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/RCTVirtualTextManager.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.flat; + +/** + * ViewManager that creates instances of RCTVirtualText. + */ +/* package */ final class RCTVirtualTextManager extends VirtualViewManager { + + @Override + public String getName() { + return "RCTVirtualText"; + } + + @Override + public RCTVirtualText createShadowNodeInstance() { + return new RCTVirtualText(); + } + + @Override + public Class getShadowNodeClass() { + return RCTVirtualText.class; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/StateBuilder.java b/ReactAndroid/src/main/java/com/facebook/react/flat/StateBuilder.java new file mode 100644 index 000000000..495af3860 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/StateBuilder.java @@ -0,0 +1,179 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.flat; + +import java.util.ArrayDeque; + +/** + * Shadow node hierarchy by itself cannot display UI, it is only a representation of what UI should + * be from JavaScript perspective. StateBuilder is a helper class that can walk the shadow node tree + * and collect information that can then be passed to UI thread and applied to a hierarchy of Views + * that Android finally can display. + */ +/* package */ final class StateBuilder { + + private final FlatUIViewOperationQueue mOperationsQueue; + + // DrawCommands + private final ArrayDeque mDrawCommands = new ArrayDeque<>(); + private DrawCommand[] mPreviousDrawCommands = DrawCommand.EMPTY_ARRAY; + private int mPreviousDrawCommandsIndex; + + /* package */ StateBuilder(FlatUIViewOperationQueue operationsQueue) { + mOperationsQueue = operationsQueue; + } + + /** + * Given a root of the laid-out shadow node hierarchy, walks the tree and generates an array of + * DrawCommands that will then mount in UI thread to a root FlatViewGroup so that it can draw. + */ + /* package*/ void applyUpdates(FlatRootShadowNode node) { + collectStateAndUpdateViewBounds(node, 0, 0); + } + + /** + * Adds a DrawCommand for current mountable node. + */ + /* package */ void addDrawCommand(AbstractDrawCommand drawCommand) { + if (mPreviousDrawCommandsIndex < mPreviousDrawCommands.length && + mPreviousDrawCommands[mPreviousDrawCommandsIndex] == drawCommand) { + ++mPreviousDrawCommandsIndex; + } else { + mPreviousDrawCommandsIndex = mPreviousDrawCommands.length + 1; + } + + mDrawCommands.addLast(drawCommand); + } + + /** + * Updates boundaries of a View that a give nodes maps to. + */ + private void updateViewBounds( + FlatRootShadowNode node, + int tag, + float leftInParent, + float topInParent, + float rightInParent, + float bottomInParent) { + int viewLeft = Math.round(leftInParent); + int viewTop = Math.round(topInParent); + int viewRight = Math.round(rightInParent); + int viewBottom = Math.round(bottomInParent); + + if (node.getViewLeft() == viewLeft && node.getViewTop() == viewTop && + node.getViewRight() == viewRight && node.getViewBottom() == viewBottom) { + // nothing changed. + return; + } + + // this will optionally measure and layout the View this node maps to. + node.setViewBounds(viewLeft, viewTop, viewRight, viewBottom); + mOperationsQueue.enqueueUpdateViewBounds(tag, viewLeft, viewTop, viewRight, viewBottom); + } + + /** + * Collects state (DrawCommands) for a given node that will mount to a View. + */ + private void collectStateForMountableNode( + FlatRootShadowNode node, + int tag, + float width, + float height) { + // save + int d = mDrawCommands.size(); + DrawCommand[] previousDrawCommands = mPreviousDrawCommands; + int previousDrawCommandsIndex = mPreviousDrawCommandsIndex; + + // reset + mPreviousDrawCommands = node.getDrawCommands(); + mPreviousDrawCommandsIndex = 0; + + collectStateRecursively(node, 0, 0, width, height); + + if (mPreviousDrawCommandsIndex != mPreviousDrawCommands.length) { + // DrawCommands changes, need to re-mount them and re-draw the View. + DrawCommand[] drawCommands = extractDrawCommands(d); + node.setDrawCommands(drawCommands); + + mOperationsQueue.enqueueUpdateMountState(tag, drawCommands); + } + + // restore + mPreviousDrawCommandsIndex = previousDrawCommandsIndex; + mPreviousDrawCommands = previousDrawCommands; + } + + /** + * Returns all DrawCommands collectes so far starting from a given index. + */ + private DrawCommand[] extractDrawCommands(int lowerBound) { + int upperBound = mDrawCommands.size(); + int size = upperBound - lowerBound; + if (size == 0) { + // avoid allocating empty array + return DrawCommand.EMPTY_ARRAY; + } + + DrawCommand[] drawCommands = new DrawCommand[size]; + for (int i = 0; i < size; ++i) { + drawCommands[i] = mDrawCommands.pollFirst(); + } + + return drawCommands; + } + + /** + * Recursively walks node tree from a given node and collects DrawCommands. + */ + private void collectStateRecursively( + FlatShadowNode node, + float left, + float top, + float right, + float bottom) { + if (node.hasNewLayout()) { + node.markLayoutSeen(); + } + + node.collectState(this, left, top, right, bottom); + + for (int i = 0, childCount = node.getChildCount(); i != childCount; ++i) { + FlatShadowNode child = (FlatShadowNode) node.getChildAt(i); + + float childLeft = left + child.getLayoutX(); + float childTop = top + child.getLayoutY(); + float childRight = childLeft + child.getLayoutWidth(); + float childBottom = childTop + child.getLayoutHeight(); + collectStateRecursively(child, childLeft, childTop, childRight, childBottom); + } + } + + /** + * Collects state and updates View boundaries for a given root node. + */ + private void collectStateAndUpdateViewBounds( + FlatRootShadowNode node, + float parentLeft, + float parentTop) { + int tag = node.getReactTag(); + + float width = node.getLayoutWidth(); + float height = node.getLayoutHeight(); + + float left = parentLeft + node.getLayoutX(); + float top = parentTop + node.getLayoutY(); + float right = left + width; + float bottom = top + height; + + collectStateForMountableNode(node, tag, width, height); + + updateViewBounds(node, tag, left, top, right, bottom); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/VirtualViewManager.java b/ReactAndroid/src/main/java/com/facebook/react/flat/VirtualViewManager.java new file mode 100644 index 000000000..04078415e --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/VirtualViewManager.java @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.flat; + +import android.view.View; + +import com.facebook.react.uimanager.ThemedReactContext; +import com.facebook.react.uimanager.ViewManager; + +/** + * Base class to ViewManagers that don't map to a View. + */ +abstract class VirtualViewManager extends ViewManager { + @Override + protected View createViewInstance(ThemedReactContext reactContext) { + throw new RuntimeException(getName() + " doesn't map to a View"); + } + + @Override + public void updateExtraData(View root, Object extraData) { + } +}