Remove implementation of Nodes (react/flat package)

Summary:
This diff removes the Nodes implementation from the source code. Why I'm deleting this code:
- I enabled nodes in catalyst app and it doesn't render even the first screen (see error images)
- This code is not being used at all inside facebook
- The last relevant commit I found was done in April 2017.
- Deleting this code make it easy to do refactors, as we don't have to modify this code.

When rendering an Image:
{F131075074}

When clicking on "RELOAD":
{F131075075}

Based on these errors I assume that this is not being used in Open Source. CC @[121800083:hramos]

Reviewed By: hramos

Differential Revision: D8640978

fbshipit-source-id: 243ee6440ebdbd24fd9f3daadc1066fd8487d9cc
This commit is contained in:
David Vacca 2018-06-26 23:44:55 -07:00 committed by Facebook Github Bot
parent c972a5c298
commit a373bf705d
64 changed files with 11 additions and 10648 deletions

View File

@ -1,130 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import javax.annotation.Nullable;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathEffect;
import android.graphics.RectF;
/**
* Base class for border drawing operations (used by DrawImage and DrawBorder). Draws rectangular or
* rounded border along the rectangle, defined by the bounding box of the AbstractDrawCommand.
*/
/* package */ abstract class AbstractDrawBorder extends AbstractDrawCommand {
private static final Paint PAINT = new Paint(Paint.ANTI_ALIAS_FLAG);
private static final RectF TMP_RECT = new RectF();
private static final int BORDER_PATH_DIRTY = 1 << 0;
static {
PAINT.setStyle(Paint.Style.STROKE);
}
private int mSetPropertiesFlag;
private int mBorderColor = Color.BLACK;
private float mBorderWidth;
private float mBorderRadius;
private @Nullable Path mPathForBorderRadius;
public final void setBorderWidth(float borderWidth) {
mBorderWidth = borderWidth;
setFlag(BORDER_PATH_DIRTY);
}
public final float getBorderWidth() {
return mBorderWidth;
}
public void setBorderRadius(float borderRadius) {
mBorderRadius = borderRadius;
setFlag(BORDER_PATH_DIRTY);
}
public final float getBorderRadius() {
return mBorderRadius;
}
public final void setBorderColor(int borderColor) {
mBorderColor = borderColor;
}
public final int getBorderColor() {
return mBorderColor;
}
@Override
protected void onBoundsChanged() {
setFlag(BORDER_PATH_DIRTY);
}
protected final void drawBorders(Canvas canvas) {
if (mBorderWidth < 0.5f) {
return;
}
if (mBorderColor == 0) {
return;
}
PAINT.setColor(mBorderColor);
PAINT.setStrokeWidth(mBorderWidth);
PAINT.setPathEffect(getPathEffectForBorderStyle());
canvas.drawPath(getPathForBorderRadius(), PAINT);
}
protected final void updatePath(Path path, float correction) {
path.reset();
TMP_RECT.set(
getLeft() + correction,
getTop() + correction,
getRight() - correction,
getBottom() - correction);
path.addRoundRect(
TMP_RECT,
mBorderRadius,
mBorderRadius,
Path.Direction.CW);
}
protected @Nullable PathEffect getPathEffectForBorderStyle() {
return null;
}
protected final boolean isFlagSet(int mask) {
return (mSetPropertiesFlag & mask) == mask;
}
protected final void setFlag(int mask) {
mSetPropertiesFlag |= mask;
}
protected final void resetFlag(int mask) {
mSetPropertiesFlag &= ~mask;
}
protected final Path getPathForBorderRadius() {
if (isFlagSet(BORDER_PATH_DIRTY)) {
if (mPathForBorderRadius == null) {
mPathForBorderRadius = new Path();
}
updatePath(mPathForBorderRadius, mBorderWidth * 0.5f);
resetFlag(BORDER_PATH_DIRTY);
}
return mPathForBorderRadius;
}
}

View File

@ -1,299 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Typeface;
/**
* 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 extends DrawCommand implements Cloneable {
private float mLeft;
private float mTop;
private float mRight;
private float mBottom;
private boolean mFrozen;
protected boolean mNeedsClipping;
private float mClipLeft;
private float mClipTop;
private float mClipRight;
private float mClipBottom;
// Used to draw highlights in debug draw.
private static Paint sDebugHighlightRed;
private static Paint sDebugHighlightYellow;
private static Paint sDebugHighlightOverlayText;
public final boolean clipBoundsMatch(
float clipLeft,
float clipTop,
float clipRight,
float clipBottom) {
return mClipLeft == clipLeft && mClipTop == clipTop
&& mClipRight == clipRight && mClipBottom == clipBottom;
}
protected final void setClipBounds(
float clipLeft,
float clipTop,
float clipRight,
float clipBottom) {
mClipLeft = clipLeft;
mClipTop = clipTop;
mClipRight = clipRight;
mClipBottom = clipBottom;
// We put this check here to not clip when we have the default [-infinity, infinity] bounds,
// since clipRect in those cases is essentially no-op anyway. This is needed to fix a bug that
// shows up during screenshot testing. Note that checking one side is enough, since if one side
// is infinite, all sides will be infinite, since we only set infinite for all sides at the
// same time - conversely, if one side is finite, all sides will be finite.
mNeedsClipping = mClipLeft != Float.NEGATIVE_INFINITY;
}
public final float getClipLeft() {
return mClipLeft;
}
public final float getClipTop() {
return mClipTop;
}
public final float getClipRight() {
return mClipRight;
}
public final float getClipBottom() {
return mClipBottom;
}
protected void applyClipping(Canvas canvas) {
canvas.clipRect(mClipLeft, mClipTop, mClipRight, mClipBottom);
}
/**
* Don't override this unless you need to do custom clipping in a draw command. Otherwise just
* override onPreDraw and onDraw.
*/
@Override
public void draw(FlatViewGroup parent, Canvas canvas) {
onPreDraw(parent, canvas);
if (mNeedsClipping && shouldClip()) {
canvas.save(Canvas.CLIP_SAVE_FLAG);
applyClipping(canvas);
onDraw(canvas);
canvas.restore();
} else {
onDraw(canvas);
}
}
protected static int getDebugBorderColor() {
return Color.CYAN;
}
protected String getDebugName() {
return getClass().getSimpleName().substring(4);
}
private void initDebugHighlightResources(FlatViewGroup parent) {
if (sDebugHighlightRed == null) {
sDebugHighlightRed = new Paint();
sDebugHighlightRed.setARGB(75, 255, 0, 0);
}
if (sDebugHighlightYellow == null) {
sDebugHighlightYellow = new Paint();
sDebugHighlightYellow.setARGB(100, 255, 204, 0);
}
if (sDebugHighlightOverlayText == null) {
sDebugHighlightOverlayText = new Paint();
sDebugHighlightOverlayText.setAntiAlias(true);
sDebugHighlightOverlayText.setARGB(200, 50, 50, 50);
sDebugHighlightOverlayText.setTextAlign(Paint.Align.RIGHT);
sDebugHighlightOverlayText.setTypeface(Typeface.MONOSPACE);
sDebugHighlightOverlayText.setTextSize(parent.dipsToPixels(9));
}
}
private void debugDrawHighlightRect(Canvas canvas, Paint paint, String text) {
canvas.drawRect(getLeft(), getTop(), getRight(), getBottom(), paint);
canvas.drawText(text, getRight() - 5, getBottom() - 5, sDebugHighlightOverlayText);
}
protected void debugDrawWarningHighlight(Canvas canvas, String text) {
debugDrawHighlightRect(canvas, sDebugHighlightRed, text);
}
protected void debugDrawCautionHighlight(Canvas canvas, String text) {
debugDrawHighlightRect(canvas, sDebugHighlightYellow, text);
}
@Override
public final void debugDraw(FlatViewGroup parent, Canvas canvas) {
onDebugDraw(parent, canvas);
if (FlatViewGroup.DEBUG_HIGHLIGHT_PERFORMANCE_ISSUES) {
initDebugHighlightResources(parent);
onDebugDrawHighlight(canvas);
}
}
protected void onDebugDraw(FlatViewGroup parent, Canvas canvas) {
parent.debugDrawNamedRect(
canvas,
getDebugBorderColor(),
getDebugName(),
mLeft,
mTop,
mRight,
mBottom);
}
protected void onDebugDrawHighlight(Canvas canvas) {
}
protected void onPreDraw(FlatViewGroup parent, Canvas canvas) {
}
/**
* Updates boundaries of the AbstractDrawCommand and freezes it.
* Will return a frozen copy if the current AbstractDrawCommand cannot be mutated.
*
* This should not be called on a DrawView, as the DrawView is modified on UI thread. Use
* DrawView.collectDrawView instead to avoid race conditions.
*/
public AbstractDrawCommand updateBoundsAndFreeze(
float left,
float top,
float right,
float bottom,
float clipLeft,
float clipTop,
float clipRight,
float clipBottom) {
if (mFrozen) {
// see if we can reuse it
boolean boundsMatch = boundsMatch(left, top, right, bottom);
boolean clipBoundsMatch = clipBoundsMatch(clipLeft, clipTop, clipRight, clipBottom);
if (boundsMatch && clipBoundsMatch) {
return this;
}
try {
AbstractDrawCommand copy = (AbstractDrawCommand) clone();
if (!boundsMatch) {
copy.setBounds(left, top, right, bottom);
}
if (!clipBoundsMatch) {
copy.setClipBounds(clipLeft, clipTop, clipRight, clipBottom);
}
return copy;
} catch (CloneNotSupportedException e) {
// This should not happen since AbstractDrawCommand implements Cloneable
throw new RuntimeException(e);
}
}
setBounds(left, top, right, bottom);
setClipBounds(clipLeft, clipTop, clipRight, clipBottom);
mFrozen = true;
return this;
}
/**
* Returns a non-frozen shallow copy of AbstractDrawCommand as defined by {@link Object#clone()}.
*/
public final AbstractDrawCommand mutableCopy() {
try {
AbstractDrawCommand copy = (AbstractDrawCommand) super.clone();
copy.mFrozen = false;
return copy;
} catch (CloneNotSupportedException e) {
// should not happen since we implement Cloneable
throw new RuntimeException(e);
}
}
/**
* Returns whether this object was frozen and thus cannot be mutated.
*/
public final boolean isFrozen() {
return mFrozen;
}
/**
* Mark this object as frozen, indicating that it should not be mutated.
*/
public final void freeze() {
mFrozen = true;
}
/**
* 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;
}
protected abstract void onDraw(Canvas canvas);
protected boolean shouldClip() {
return mLeft < getClipLeft() || mTop < getClipTop() ||
mRight > getClipRight() || mBottom > getClipBottom();
}
protected void onBoundsChanged() {
}
/**
* Updates boundaries of this DrawCommand.
*/
protected final void setBounds(float left, float top, float right, float bottom) {
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
onBoundsChanged();
}
/**
* Returns true if boundaries match and don't need to be updated. False otherwise.
*/
protected final boolean boundsMatch(float left, float top, float right, float bottom) {
return mLeft == left && mTop == top && mRight == right && mBottom == bottom;
}
}

View File

@ -1,33 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
interface AndroidView {
/**
* Whether or not custom layout is needed for the children
* @return a boolean representing whether custom layout is needed
*/
boolean needsCustomLayoutForChildren();
/**
* Did the padding change
* @return a boolean representing whether the padding changed
*/
boolean isPaddingChanged();
/**
* Reset the padding changed internal state
*/
void resetPaddingChanged();
/**
* Get the padding for a certain spacingType defined in com.facebook.yoga.Spacing
*/
float getPadding(int spacingType);
}

View File

@ -1,28 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
/**
* An interface that DrawCommands need to implement into order to receive
* {@link android.view.View#onAttachedToWindow()} and
* {@link android.view.View#onDetachedFromWindow()} events.
*/
/* package */ interface AttachDetachListener {
public static final AttachDetachListener[] EMPTY_ARRAY = new AttachDetachListener[0];
/**
* Called when a DrawCommand is being attached to a visible View hierarchy.
* @param callback a WeakReference to a View that provides invalidate() helper method.
*/
public void onAttached(FlatViewGroup.InvalidateCallback callback);
/**
* Called when a DrawCommand is being detached from a visible View hierarchy.
*/
public void onDetached();
}

View File

@ -1,38 +0,0 @@
load("//ReactNative:DEFS.bzl", "YOGA_TARGET", "react_native_dep", "react_native_target", "rn_android_library")
rn_android_library(
name = "flat",
srcs = glob(["*.java"]),
provided_deps = [
react_native_dep("third-party/android/support/v4:lib-support-v4"),
],
visibility = [
"PUBLIC",
],
deps = [
YOGA_TARGET,
react_native_dep("libraries/fbcore/src/main/java/com/facebook/common/logging:logging"),
react_native_dep("libraries/fresco/fresco-react-native:fbcore"),
react_native_dep("libraries/fresco/fresco-react-native:fresco-drawee"),
react_native_dep("libraries/fresco/fresco-react-native:fresco-react-native"),
react_native_dep("libraries/fresco/fresco-react-native:imagepipeline"),
react_native_dep("libraries/textlayoutbuilder:textlayoutbuilder"),
react_native_dep("third-party/java/infer-annotations:infer-annotations"),
react_native_dep("third-party/java/jsr-305:jsr-305"),
react_native_target("java/com/facebook/react/bridge:bridge"),
react_native_target("java/com/facebook/react/common:common"),
react_native_target("java/com/facebook/react/modules/fresco:fresco"),
react_native_target("java/com/facebook/react/modules/i18nmanager:i18nmanager"),
react_native_target("java/com/facebook/react/touch:touch"),
react_native_target("java/com/facebook/react/uimanager:uimanager"),
react_native_target("java/com/facebook/react/uimanager/annotations:annotations"),
react_native_target("java/com/facebook/react/views/art:art"),
react_native_target("java/com/facebook/react/views/image:image"),
react_native_target("java/com/facebook/react/views/imagehelper:withmultisource"),
react_native_target("java/com/facebook/react/views/modal:modal"),
react_native_target("java/com/facebook/react/views/text:text"),
react_native_target("java/com/facebook/react/views/textinput:textinput"),
react_native_target("java/com/facebook/react/views/view:view"),
react_native_target("java/com/facebook/react/views/viewpager:viewpager"),
],
)

View File

@ -1,16 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import android.graphics.Bitmap;
/* package */ interface BitmapUpdateListener {
public void onSecondaryAttach(Bitmap bitmap);
public void onBitmapReady(Bitmap bitmap);
public void onImageLoadEvent(int imageLoadEvent);
}

View File

@ -1,650 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.View;
import android.view.animation.Animation;
import com.facebook.common.logging.FLog;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.uimanager.ReactClippingViewGroup;
import com.facebook.react.uimanager.ReactClippingViewGroupHelper;
/**
* Abstract class for a {@link DrawCommandManager} with directional clipping. Allows support for
* vertical and horizontal clipping by implementing abstract methods.
*
* Uses two dynamic programming arrays to efficiently update which views and commands are onscreen,
* while not having to sort the incoming draw commands. The draw commands are loosely sorted, as
* they represent a flattening of the normal view hierarchy, and we use that information to quickly
* find children that should be considered onscreen. One array keeps track of, for each index, the
* maximum bottom position that occurs at or before that index; the other keeps track of the
* minimum top position that occurs at or after that index. Given the following children:
*
* +---------------------------------+ 0 (Y coordinate)
* | 0 |
* | +-----------+ | 10
* | | 1 | |
* | | | +--------------+ | 20
* | | | | 3 | |
* | +-----------+ | | | 30
* | | | |
* | +-----------+ | | | 40
* | | 2 | | | |
* | | | +--------------+ | 50
* | | | |
* | +-----------+ | 60
* | |
* +---------------------------------+ 70
*
* +-----------+ 80
* | 4 |
* | | +--------------+ 90
* | | | 6 |
* +-----------+ | | 100
* | |
* +-----------+ | | 110
* | 5 | | |
* | | +--------------+ 120
* | |
* +-----------+ 130
*
* The two arrays are:
* 0 1 2 3 4 5 6
* Max Bottom: [70, 70, 70, 70, 100, 130, 130]
* Min Top: [ 0, 0, 0, 0, 80, 90, 90]
*
* We can then binary search for the first max bottom that is above our rect, and the first min top
* that is below our rect.
*
* If the top and bottom of the rect are 55 and 85, respectively, we will start drawing at index 0
* and stop at index 4.
*
* +---------------------------------+ 0 (Y coordinate)
* | 0 |
* | +-----------+ | 10
* | | 1 | |
* | | | +--------------+ | 20
* | | | | 3 | |
* | +-----------+ | | | 30
* | | | |
* | +-----------+ | | | 40
* | | 2 | | | |
* | | | +--------------+ | 50
* - -| -| - - - -| - - - - - - |- - -
* | +-----------+ | 60
* | |
* +---------------------------------+ 70
*
* +-----------+ 80
* - - -| 4 - - - | - - - - - - - - -
* | | +--------------+ 90
* | | | 6 |
* +-----------+ | | 100
* | |
* +-----------+ | | 110
* | 5 | | |
* | | +--------------+ 120
* | |
* +-----------+ 130
*
* If the top and bottom are 75 and 105 respectively, we will start drawing at index 4 and stop at
* index 6.
*
* +---------------------------------+ 0 (Y coordinate)
* | 0 |
* | +-----------+ | 10
* | | 1 | |
* | | | +--------------+ | 20
* | | | | 3 | |
* | +-----------+ | | | 30
* | | | |
* | +-----------+ | | | 40
* | | 2 | | | |
* | | | +--------------+ | 50
* | | | |
* | +-----------+ | 60
* | |
* +---------------------------------+ 70
* - - - - - - - - - - - - - - - -
* +-----------+ 80
* | 4 |
* | | +--------------+ 90
* | | | 6 |
* +-----------+ | | 100
* - - - - - - - |- - - - - | - - -
* +-----------+ | | 110
* | 5 | | |
* | | +--------------+ 120
* | |
* +-----------+ 130
*
* While this doesn't map exactly to all of the commands that could be clipped, it means that
* children which contain other children (a pretty common case when flattening views) are clipped
* or unclipped as one logical unit. This has the side effect of minimizing the amount of
* invalidates coming from minor clipping rect adjustments. The underlying dynamic programming
* arrays can be calculated off the UI thread in O(n) time, requiring just two passes through the
* command array.
*
* We do a similar optimization when searching for matching node regions, as node regions are
* loosely sorted as well when clipping.
*/
/* package */ abstract class ClippingDrawCommandManager extends DrawCommandManager {
private static final String TAG = ClippingDrawCommandManager.class.getSimpleName();
private final FlatViewGroup mFlatViewGroup;
private DrawCommand[] mDrawCommands = DrawCommand.EMPTY_ARRAY;
protected float[] mCommandMaxBottom = StateBuilder.EMPTY_FLOAT_ARRAY;
protected float[] mCommandMinTop = StateBuilder.EMPTY_FLOAT_ARRAY;
private NodeRegion[] mNodeRegions = NodeRegion.EMPTY_ARRAY;
protected float[] mRegionMaxBottom = StateBuilder.EMPTY_FLOAT_ARRAY;
protected float[] mRegionMinTop = StateBuilder.EMPTY_FLOAT_ARRAY;
// Onscreen bounds of draw command array.
private int mStart;
private int mStop;
// Mapping of ids to index position within the draw command array. O(log n) lookups should be
// less in our case because of the large constant overhead and auto boxing of the map.
private SparseIntArray mDrawViewIndexMap = StateBuilder.EMPTY_SPARSE_INT;
// Map of views that are currently clipped.
private final SparseArray<View> mClippedSubviews = new SparseArray<>();
protected final Rect mClippingRect = new Rect();
// Used in updating the clipping rect, as sometimes we want to detach all views, which means we
// need to temporarily store the views we are detaching and removing. These are always of size
// 0, except when used in update clipping rect.
private final SparseArray<View> mViewsToRemove = new SparseArray<>();
private final ArrayList<View> mViewsToKeep = new ArrayList<>();
// Currently clipping ViewGroups
private final ArrayList<ReactClippingViewGroup> mClippingViewGroups = new ArrayList<>();
ClippingDrawCommandManager(FlatViewGroup flatViewGroup, DrawCommand[] drawCommands) {
mFlatViewGroup = flatViewGroup;
initialSetup(drawCommands);
}
/**
* Initially setup this instance. Makes sure the draw commands are mounted, and that our
* clipping rect reflects our current bounds.
*
* @param drawCommands The list of current draw commands. In current implementations, this will
* always be DrawCommand.EMPTY_ARRAY
*/
private void initialSetup(DrawCommand[] drawCommands) {
mountDrawCommands(
drawCommands,
mDrawViewIndexMap,
mCommandMaxBottom,
mCommandMinTop,
true);
updateClippingRect();
}
/**
* @return index of the first command that could be on the screen.
*/
abstract int commandStartIndex();
/**
* @return index of the first command that is guaranteed to be off the screen, starting from the
* given start.
*/
abstract int commandStopIndex(int start);
/**
* @return index of the first region that is guaranteed to be outside of the bounds for touch.
*/
abstract int regionStopIndex(float touchX, float touchY);
/**
* Whether an index and all indices before it are guaranteed to be out of bounds for the current
* touch.
*
* @param index The region index to check.
* @param touchX X coordinate.
* @param touchY Y coordinate.
* @return true if the index and all before it are out of bounds.
*/
abstract boolean regionAboveTouch(int index, float touchX, float touchY);
@Override
public void mountDrawCommands(
DrawCommand[] drawCommands,
SparseIntArray drawViewIndexMap,
float[] maxBottom,
float[] minTop,
boolean willMountViews) {
mDrawCommands = drawCommands;
mCommandMaxBottom = maxBottom;
mCommandMinTop = minTop;
mDrawViewIndexMap = drawViewIndexMap;
if (mClippingRect.bottom != mClippingRect.top) {
mStart = commandStartIndex();
mStop = commandStopIndex(mStart);
if (!willMountViews) {
// If we are not mounting views, we still need to update view indices and positions. It is
// possible that a child changed size and we still need new clipping even though we are not
// mounting views.
updateClippingToCurrentRect();
}
}
}
@Override
public void mountNodeRegions(NodeRegion[] nodeRegions, float[] maxBottom, float[] minTop) {
mNodeRegions = nodeRegions;
mRegionMaxBottom = maxBottom;
mRegionMinTop = minTop;
}
@Override
public @Nullable NodeRegion virtualNodeRegionWithinBounds(float touchX, float touchY) {
int i = regionStopIndex(touchX, touchY);
while (i-- > 0) {
NodeRegion nodeRegion = mNodeRegions[i];
if (!nodeRegion.mIsVirtual) {
// only interested in virtual nodes
continue;
}
if (regionAboveTouch(i, touchX, touchY)) {
break;
}
if (nodeRegion.withinBounds(touchX, touchY)) {
return nodeRegion;
}
}
return null;
}
@Override
public @Nullable NodeRegion anyNodeRegionWithinBounds(float touchX, float touchY) {
int i = regionStopIndex(touchX, touchY);
while (i-- > 0) {
NodeRegion nodeRegion = mNodeRegions[i];
if (regionAboveTouch(i, touchX, touchY)) {
break;
}
if (nodeRegion.withinBounds(touchX, touchY)) {
return nodeRegion;
}
}
return null;
}
private void clip(int id, View view) {
mClippedSubviews.put(id, view);
}
private void unclip(int id) {
mClippedSubviews.remove(id);
}
private boolean isClipped(int id) {
return mClippedSubviews.get(id) != null;
}
private boolean isNotClipped(int id) {
return mClippedSubviews.get(id) == null;
}
@Override
void onClippedViewDropped(View view) {
unclip(view.getId());
mFlatViewGroup.removeDetachedView(view);
}
@Override
public void mountViews(ViewResolver viewResolver, int[] viewsToAdd, int[] viewsToDetach) {
mClippingViewGroups.clear();
for (int viewToAdd : viewsToAdd) {
// Views that are just temporarily detached are marked with a negative value.
boolean newView = viewToAdd > 0;
if (!newView) {
viewToAdd = -viewToAdd;
}
int commandArrayIndex = mDrawViewIndexMap.get(viewToAdd);
DrawView drawView = (DrawView) mDrawCommands[commandArrayIndex];
View view = viewResolver.getView(drawView.reactTag);
ensureViewHasNoParent(view);
// these views need to support recursive clipping of subviews
if (view instanceof ReactClippingViewGroup &&
((ReactClippingViewGroup) view).getRemoveClippedSubviews()) {
mClippingViewGroups.add((ReactClippingViewGroup) view);
}
if (newView) {
// This view was not previously attached to this parent.
drawView.mWasMounted = true;
if (animating(view) || withinBounds(commandArrayIndex)) {
// View should be drawn. This view can't currently be clipped because it wasn't
// previously attached to this parent.
mFlatViewGroup.addViewInLayout(view);
} else {
clip(drawView.reactTag, view);
}
} else {
// This view was previously attached, and just temporarily detached.
if (drawView.mWasMounted) {
// The DrawView has been mounted before.
if (isNotClipped(drawView.reactTag)) {
// The DrawView is not clipped. Attach it.
mFlatViewGroup.attachViewToParent(view);
}
// else The DrawView has been previously mounted and is clipped, so don't attach it.
} else {
// We are mounting it, so lets get this part out of the way.
drawView.mWasMounted = true;
// The DrawView has not been mounted before, which means the bounds changed and triggered
// a new DrawView when it was collected from the shadow node. We have a view with the
// same id temporarily detached, but its bounds have changed.
if (animating(view) || withinBounds(commandArrayIndex)) {
// View should be drawn.
if (isClipped(drawView.reactTag)) {
// View was clipped, so add it.
mFlatViewGroup.addViewInLayout(view);
unclip(drawView.reactTag);
} else {
// View was just temporarily removed, so attach it. We already know it isn't clipped,
// so no need to unclip it.
mFlatViewGroup.attachViewToParent(view);
}
} else {
// View should be clipped.
if (isNotClipped(drawView.reactTag)) {
// View was onscreen.
mFlatViewGroup.removeDetachedView(view);
clip(drawView.reactTag, view);
}
// else view is already clipped and not within bounds.
}
}
}
}
for (int viewToDetach : viewsToDetach) {
View view = viewResolver.getView(viewToDetach);
if (view.getParent() != null) {
throw new RuntimeException("Trying to remove view not owned by FlatViewGroup");
} else {
mFlatViewGroup.removeDetachedView(view);
}
// The view isn't clipped anymore, but gone entirely.
unclip(viewToDetach);
}
}
/**
* Returns true if a view is currently animating.
*/
private static boolean animating(View view) {
Animation animation = view.getAnimation();
return animation != null && !animation.hasEnded();
}
/**
* Returns true if a command index is currently onscreen.
*/
private boolean withinBounds(int i) {
return mStart <= i && i < mStop;
}
@Override
public boolean updateClippingRect() {
ReactClippingViewGroupHelper.calculateClippingRect(mFlatViewGroup, mClippingRect);
if (mFlatViewGroup.getParent() == null || mClippingRect.top == mClippingRect.bottom) {
// If we are unparented or are clipping to an empty rect, no op. Return false so we don't
// invalidate.
return false;
}
int start = commandStartIndex();
int stop = commandStopIndex(start);
if (mStart <= start && stop <= mStop) {
// We would only be removing children, don't invalidate and don't bother changing the
// attached children.
updateClippingRecursively();
return false;
}
mStart = start;
mStop = stop;
updateClippingToCurrentRect();
updateClippingRecursively();
return true;
}
private void updateClippingRecursively() {
for (int i = 0, children = mClippingViewGroups.size(); i < children; i++) {
ReactClippingViewGroup view = mClippingViewGroups.get(i);
if (isNotClipped(((View) view).getId())) {
view.updateClippingRect();
}
}
}
/**
* Used either after we have updated the current rect, or when we have mounted new commands and
* the rect hasn't changed. Updates the clipping after mStart and mStop have been set to the
* correct values. For draw commands, this is all it takes to update the command mounting, as
* draw commands are only attached in a conceptual sense, and don't rely on the android view
* hierarchy.
*
* For native children, we have to walk through our current views and remove any that are no
* longer on screen, and add those that are newly on screen. As an optimization for fling, if we
* are removing two or more native views we instead detachAllViews from the {@link FlatViewGroup}
* and re-attach or add as needed.
*
* This approximation is roughly correct, as we tend to add and remove the same amount of views,
* and each add and remove pair is O(n); detachAllViews and re-attach requires two passes, so
* using this once we are removing more than two native views is a good breakpoint.
*/
private void updateClippingToCurrentRect() {
for (int i = 0, size = mFlatViewGroup.getChildCount(); i < size; i++) {
View view = mFlatViewGroup.getChildAt(i);
int index = mDrawViewIndexMap.get(view.getId());
if (withinBounds(index) || animating(view)) {
mViewsToKeep.add(view);
} else {
mViewsToRemove.append(i, view);
clip(view.getId(), view);
}
}
int removeSize = mViewsToRemove.size();
boolean removeAll = removeSize > 2;
if (removeAll) {
// Detach all, as we are changing quite a few views, whether flinging or otherwise.
mFlatViewGroup.detachAllViewsFromParent();
for (int i = 0; i < removeSize; i++) {
mFlatViewGroup.removeDetachedView(mViewsToRemove.valueAt(i));
}
} else {
// Simple clipping sweep, as we are changing relatively few views.
while (removeSize-- > 0) {
mFlatViewGroup.removeViewsInLayout(mViewsToRemove.keyAt(removeSize), 1);
}
}
mViewsToRemove.clear();
int current = mStart;
int childIndex = 0;
for (int i = 0, size = mViewsToKeep.size(); i < size; i++) {
View view = mViewsToKeep.get(i);
int commandIndex = mDrawViewIndexMap.get(view.getId());
if (current <= commandIndex) {
while (current != commandIndex) {
if (mDrawCommands[current] instanceof DrawView) {
DrawView drawView = (DrawView) mDrawCommands[current];
mFlatViewGroup.addViewInLayout(
Assertions.assumeNotNull(mClippedSubviews.get(drawView.reactTag)),
childIndex++);
unclip(drawView.reactTag);
}
current++;
}
// We are currently at the command index, but we want to increment beyond it.
current++;
}
if (removeAll) {
mFlatViewGroup.attachViewToParent(view, childIndex);
}
// We want to make sure we increment the child index even if we didn't detach it to maintain
// order.
childIndex++;
}
mViewsToKeep.clear();
while (current < mStop) {
if (mDrawCommands[current] instanceof DrawView) {
DrawView drawView = (DrawView) mDrawCommands[current];
mFlatViewGroup.addViewInLayout(
Assertions.assumeNotNull(mClippedSubviews.get(drawView.reactTag)),
childIndex++);
unclip(drawView.reactTag);
}
current++;
}
}
@Override
public void getClippingRect(Rect outClippingRect) {
outClippingRect.set(mClippingRect);
}
@Override
public SparseArray<View> getDetachedViews() {
return mClippedSubviews;
}
/**
* Draws the unclipped commands on the given canvas. This would be much simpler if we didn't
* have to worry about animating views, as we could simply:
*
* for (int i = start; i < stop; i++) {
* drawCommands[i].draw(...);
* }
*
* This is complicated however by animating views, which may occur before or after the current
* clipping rect. Consider the following array:
*
* +--------------+
* | DrawView | 0
* | *animating* |
* +--------------+
* | DrawCommmand | 1
* | *clipped* |
* +--------------+
* | DrawCommand | 2 start
* | |
* +--------------+
* | DrawCommand | 3
* | |
* +--------------+
* | DrawView | 4
* | |
* +--------------+
* | DrawView | 5 stop
* | *clipped* |
* +--------------+
* | DrawView | 6
* | *animating* |
* +--------------+
*
* 2, 3, and 4 are onscreen according to bounds, while 0 and 6 are onscreen according to
* animation. We have to walk through the attached children making sure to draw any draw
* commands that should be drawn before that draw view, as well as making sure not to draw any
* draw commands that are out of bounds.
*
* @param canvas The canvas to draw on.
*/
@Override
public void draw(Canvas canvas) {
int commandIndex = mStart;
int size = mFlatViewGroup.getChildCount();
// Iterate through the children, making sure that we draw any draw commands we haven't drawn
// that should happen before the next draw view.
for (int i = 0; i < size; i++) {
// This is the command index of the next view that we need to draw. Since a view might be
// animating, this view is either before all the commands onscreen, onscreen, or after the
// onscreen commands.
int viewIndex = mDrawViewIndexMap.get(mFlatViewGroup.getChildAt(i).getId());
if (mStop < viewIndex) {
// The current view is outside of the viewport bounds. We want to draw all the commands
// up to the stop, then draw all the views outside the viewport bounds.
while (commandIndex < mStop) {
mDrawCommands[commandIndex++].draw(mFlatViewGroup, canvas);
}
// We are now out of commands to draw, so we could just draw the remaining attached
// children, but the for loop logic will draw the rest anyway.
} else if (commandIndex <= viewIndex) {
// The viewIndex is within our onscreen bounds (or == stop). We want to draw all the
// commands from the current position to the current view, inclusive.
while (commandIndex < viewIndex) {
mDrawCommands[commandIndex++].draw(mFlatViewGroup, canvas);
}
// Command index now == viewIndex, so increment beyond it.
commandIndex++;
}
mDrawCommands[viewIndex].draw(mFlatViewGroup, canvas);
}
// If we get here, it means we have drawn all the views, now just draw the remaining draw
// commands.
while (commandIndex < mStop) {
DrawCommand command = mDrawCommands[commandIndex++];
if (command instanceof DrawView) {
// We should never have more DrawView commands at this point. But in case we do, fail safely
// by ignoring the DrawView command
FLog.w(
TAG,
"Unexpected DrawView command at index " + (commandIndex-1) + " with mStop=" +
mStop + ". " + Arrays.toString(mDrawCommands));
continue;
}
command.draw(mFlatViewGroup, canvas);
}
}
@Override
void debugDraw(Canvas canvas) {
// Draws clipped draw commands, but does not draw clipped views.
for (DrawCommand drawCommand : mDrawCommands) {
if (drawCommand instanceof DrawView) {
if (isNotClipped(((DrawView) drawCommand).reactTag)) {
drawCommand.debugDraw(mFlatViewGroup, canvas);
}
// else, don't draw, and don't increment index
} else {
drawCommand.debugDraw(mFlatViewGroup, canvas);
}
}
}
}

View File

@ -1,31 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import android.graphics.Canvas;
import android.graphics.Paint;
/**
* Draws background for a FlatShadowNode as a solid rectangle.
*/
/* package */ final class DrawBackgroundColor extends AbstractDrawCommand {
private static final Paint PAINT = new Paint();
private final int mBackgroundColor;
/* package */ DrawBackgroundColor(int backgroundColor) {
mBackgroundColor = backgroundColor;
}
@Override
public void onDraw(Canvas canvas) {
PAINT.setColor(mBackgroundColor);
canvas.drawRect(getLeft(), getTop(), getRight(), getBottom(), PAINT);
}
}

View File

@ -1,458 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import javax.annotation.Nullable;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import com.facebook.react.uimanager.Spacing;
/* package */ final class DrawBorder extends AbstractDrawBorder {
private static final Paint PAINT = new Paint(Paint.ANTI_ALIAS_FLAG);
private static final float[] TMP_FLOAT_ARRAY = new float[4];
private static final int BORDER_STYLE_SOLID = 0;
private static final int BORDER_STYLE_DOTTED = 1;
private static final int BORDER_STYLE_DASHED = 2;
private static final int BORDER_LEFT_COLOR_SET = 1 << 1;
private static final int BORDER_TOP_COLOR_SET = 1 << 2;
private static final int BORDER_RIGHT_COLOR_SET = 1 << 3;
private static final int BORDER_BOTTOM_COLOR_SET = 1 << 4;
private static final int BORDER_PATH_EFFECT_DIRTY = 1 << 5;
// ~0 == 0xFFFFFFFF, all bits set to 1.
private static final int ALL_BITS_SET = ~0;
// 0 == 0x00000000, all bits set to 0.
private static final int ALL_BITS_UNSET = 0;
private float mBorderLeftWidth;
private float mBorderTopWidth;
private float mBorderRightWidth;
private float mBorderBottomWidth;
private int mBorderLeftColor;
private int mBorderTopColor;
private int mBorderRightColor;
private int mBorderBottomColor;
private int mBorderStyle = BORDER_STYLE_SOLID;
private int mBackgroundColor;
private @Nullable DashPathEffect mPathEffectForBorderStyle;
private @Nullable Path mPathForBorder;
public void setBorderWidth(int position, float borderWidth) {
switch (position) {
case Spacing.LEFT:
mBorderLeftWidth = borderWidth;
break;
case Spacing.TOP:
mBorderTopWidth = borderWidth;
break;
case Spacing.RIGHT:
mBorderRightWidth = borderWidth;
break;
case Spacing.BOTTOM:
mBorderBottomWidth = borderWidth;
break;
case Spacing.ALL:
setBorderWidth(borderWidth);
break;
}
}
public float getBorderWidth(int position) {
switch (position) {
case Spacing.LEFT:
return mBorderLeftWidth;
case Spacing.TOP:
return mBorderTopWidth;
case Spacing.RIGHT:
return mBorderRightWidth;
case Spacing.BOTTOM:
return mBorderBottomWidth;
case Spacing.ALL:
return getBorderWidth();
}
return 0.0f;
}
public void setBorderStyle(@Nullable String style) {
if ("dotted".equals(style)) {
mBorderStyle = BORDER_STYLE_DOTTED;
} else if ("dashed".equals(style)) {
mBorderStyle = BORDER_STYLE_DASHED;
} else {
mBorderStyle = BORDER_STYLE_SOLID;
}
setFlag(BORDER_PATH_EFFECT_DIRTY);
}
public void resetBorderColor(int position) {
switch (position) {
case Spacing.LEFT:
resetFlag(BORDER_LEFT_COLOR_SET);
break;
case Spacing.TOP:
resetFlag(BORDER_TOP_COLOR_SET);
break;
case Spacing.RIGHT:
resetFlag(BORDER_RIGHT_COLOR_SET);
break;
case Spacing.BOTTOM:
resetFlag(BORDER_BOTTOM_COLOR_SET);
break;
case Spacing.ALL:
setBorderColor(Color.BLACK);
break;
}
}
public void setBorderColor(int position, int borderColor) {
switch (position) {
case Spacing.LEFT:
mBorderLeftColor = borderColor;
setFlag(BORDER_LEFT_COLOR_SET);
break;
case Spacing.TOP:
mBorderTopColor = borderColor;
setFlag(BORDER_TOP_COLOR_SET);
break;
case Spacing.RIGHT:
mBorderRightColor = borderColor;
setFlag(BORDER_RIGHT_COLOR_SET);
break;
case Spacing.BOTTOM:
mBorderBottomColor = borderColor;
setFlag(BORDER_BOTTOM_COLOR_SET);
break;
case Spacing.ALL:
setBorderColor(borderColor);
break;
}
}
public int getBorderColor(int position) {
int defaultColor = getBorderColor();
switch (position) {
case Spacing.LEFT:
return resolveBorderColor(BORDER_LEFT_COLOR_SET, mBorderLeftColor, defaultColor);
case Spacing.TOP:
return resolveBorderColor(BORDER_TOP_COLOR_SET, mBorderTopColor, defaultColor);
case Spacing.RIGHT:
return resolveBorderColor(BORDER_RIGHT_COLOR_SET, mBorderRightColor, defaultColor);
case Spacing.BOTTOM:
return resolveBorderColor(BORDER_BOTTOM_COLOR_SET, mBorderBottomColor, defaultColor);
case Spacing.ALL:
return defaultColor;
}
return defaultColor;
}
public void setBackgroundColor(int backgroundColor) {
mBackgroundColor = backgroundColor;
}
public int getBackgroundColor() {
return mBackgroundColor;
}
@Override
protected void onDraw(Canvas canvas) {
if (getBorderRadius() >= 0.5f || getPathEffectForBorderStyle() != null) {
drawRoundedBorders(canvas);
} else {
drawRectangularBorders(canvas);
}
}
@Override
protected @Nullable DashPathEffect getPathEffectForBorderStyle() {
if (isFlagSet(BORDER_PATH_EFFECT_DIRTY)) {
switch (mBorderStyle) {
case BORDER_STYLE_DOTTED:
mPathEffectForBorderStyle = createDashPathEffect(getBorderWidth());
break;
case BORDER_STYLE_DASHED:
mPathEffectForBorderStyle = createDashPathEffect(getBorderWidth() * 3);
break;
default:
mPathEffectForBorderStyle = null;
break;
}
resetFlag(BORDER_PATH_EFFECT_DIRTY);
}
return mPathEffectForBorderStyle;
}
private void drawRoundedBorders(Canvas canvas) {
if (mBackgroundColor != 0) {
PAINT.setColor(mBackgroundColor);
canvas.drawPath(getPathForBorderRadius(), PAINT);
}
drawBorders(canvas);
}
/**
* Quickly determine if all the set border colors are equal. Bitwise AND all the set colors
* together, then OR them all together. If the AND and the OR are the same, then the colors
* are compatible, so return this color.
*
* Used to avoid expensive path creation and expensive calls to canvas.drawPath
*
* @return A compatible border color, or zero if the border colors are not compatible.
*/
private static int fastBorderCompatibleColorOrZero(
float borderLeft,
float borderTop,
float borderRight,
float borderBottom,
int colorLeft,
int colorTop,
int colorRight,
int colorBottom) {
int andSmear = (borderLeft > 0 ? colorLeft : ALL_BITS_SET) &
(borderTop > 0 ? colorTop : ALL_BITS_SET) &
(borderRight > 0 ? colorRight : ALL_BITS_SET) &
(borderBottom > 0 ? colorBottom : ALL_BITS_SET);
int orSmear = (borderLeft > 0 ? colorLeft : ALL_BITS_UNSET) |
(borderTop > 0 ? colorTop : ALL_BITS_UNSET) |
(borderRight > 0 ? colorRight : ALL_BITS_UNSET) |
(borderBottom > 0 ? colorBottom : ALL_BITS_UNSET);
return andSmear == orSmear ? andSmear : 0;
}
private void drawRectangularBorders(Canvas canvas) {
int defaultColor = getBorderColor();
float defaultWidth = getBorderWidth();
float top = getTop();
float borderTop = resolveWidth(mBorderTopWidth, defaultWidth);
float topInset = top + borderTop;
int topColor = resolveBorderColor(BORDER_TOP_COLOR_SET, mBorderTopColor, defaultColor);
float bottom = getBottom();
float borderBottom = resolveWidth(mBorderBottomWidth, defaultWidth);
float bottomInset = bottom - borderBottom;
int bottomColor = resolveBorderColor(BORDER_BOTTOM_COLOR_SET, mBorderBottomColor, defaultColor);
float left = getLeft();
float borderLeft = resolveWidth(mBorderLeftWidth, defaultWidth);
float leftInset = left + borderLeft;
int leftColor = resolveBorderColor(BORDER_LEFT_COLOR_SET, mBorderLeftColor, defaultColor);
float right = getRight();
float borderRight = resolveWidth(mBorderRightWidth, defaultWidth);
float rightInset = right - borderRight;
int rightColor = resolveBorderColor(BORDER_RIGHT_COLOR_SET, mBorderRightColor, defaultColor);
// Check for fast path to border drawing.
int fastBorderColor = fastBorderCompatibleColorOrZero(
borderLeft,
borderTop,
borderRight,
borderBottom,
leftColor,
topColor,
rightColor,
bottomColor);
if (fastBorderColor != 0) {
// Fast border color draw.
if (Color.alpha(fastBorderColor) != 0) {
// Border color is not transparent.
// Draw center.
if (Color.alpha(mBackgroundColor) != 0) {
PAINT.setColor(mBackgroundColor);
if (Color.alpha(fastBorderColor) == 255) {
// The border will draw over the edges, so only draw the inset background.
canvas.drawRect(leftInset, topInset, rightInset, bottomInset, PAINT);
} else {
// The border is opaque, so we have to draw the entire background color.
canvas.drawRect(left, top, right, bottom, PAINT);
}
}
PAINT.setColor(fastBorderColor);
if (borderLeft > 0) {
canvas.drawRect(left, top, leftInset, bottom - borderBottom, PAINT);
}
if (borderTop > 0) {
canvas.drawRect(left + borderLeft, top, right, topInset, PAINT);
}
if (borderRight > 0) {
canvas.drawRect(rightInset, top + borderTop, right, bottom, PAINT);
}
if (borderBottom > 0) {
canvas.drawRect(left, bottomInset, right - borderRight, bottom, PAINT);
}
}
} else {
if (mPathForBorder == null) {
mPathForBorder = new Path();
}
// Draw center. Any of the borders might be opaque or transparent, so we need to draw this.
if (Color.alpha(mBackgroundColor) != 0) {
PAINT.setColor(mBackgroundColor);
canvas.drawRect(left, top, right, bottom, PAINT);
}
// Draw top.
if (borderTop != 0 && Color.alpha(topColor) != 0) {
PAINT.setColor(topColor);
updatePathForTopBorder(
mPathForBorder,
top,
topInset,
left,
leftInset,
right,
rightInset);
canvas.drawPath(mPathForBorder, PAINT);
}
// Draw bottom.
if (borderBottom != 0 && Color.alpha(bottomColor) != 0) {
PAINT.setColor(bottomColor);
updatePathForBottomBorder(
mPathForBorder,
bottom,
bottomInset,
left,
leftInset,
right,
rightInset);
canvas.drawPath(mPathForBorder, PAINT);
}
// Draw left.
if (borderLeft != 0 && Color.alpha(leftColor) != 0) {
PAINT.setColor(leftColor);
updatePathForLeftBorder(
mPathForBorder,
top,
topInset,
bottom,
bottomInset,
left,
leftInset);
canvas.drawPath(mPathForBorder, PAINT);
}
// Draw right.
if (borderRight != 0 && Color.alpha(rightColor) != 0) {
PAINT.setColor(rightColor);
updatePathForRightBorder(
mPathForBorder,
top,
topInset,
bottom,
bottomInset,
right,
rightInset);
canvas.drawPath(mPathForBorder, PAINT);
}
}
}
private static void updatePathForTopBorder(
Path path,
float top,
float topInset,
float left,
float leftInset,
float right,
float rightInset) {
path.reset();
path.moveTo(left, top);
path.lineTo(leftInset, topInset);
path.lineTo(rightInset, topInset);
path.lineTo(right, top);
path.lineTo(left, top);
}
private static void updatePathForBottomBorder(
Path path,
float bottom,
float bottomInset,
float left,
float leftInset,
float right,
float rightInset) {
path.reset();
path.moveTo(left, bottom);
path.lineTo(right, bottom);
path.lineTo(rightInset, bottomInset);
path.lineTo(leftInset, bottomInset);
path.lineTo(left, bottom);
}
private static void updatePathForLeftBorder(
Path path,
float top,
float topInset,
float bottom,
float bottomInset,
float left,
float leftInset) {
path.reset();
path.moveTo(left, top);
path.lineTo(leftInset, topInset);
path.lineTo(leftInset, bottomInset);
path.lineTo(left, bottom);
path.lineTo(left, top);
}
private static void updatePathForRightBorder(
Path path,
float top,
float topInset,
float bottom,
float bottomInset,
float right,
float rightInset) {
path.reset();
path.moveTo(right, top);
path.lineTo(right, bottom);
path.lineTo(rightInset, bottomInset);
path.lineTo(rightInset, topInset);
path.lineTo(right, top);
}
private int resolveBorderColor(int flag, int color, int defaultColor) {
return isFlagSet(flag) ? color : defaultColor;
}
private static float resolveWidth(float width, float defaultWidth) {
return (width == 0 || /* check for NaN */ width != width) ? defaultWidth : width;
}
private static DashPathEffect createDashPathEffect(float borderWidth) {
for (int i = 0; i < 4; ++i) {
TMP_FLOAT_ARRAY[i] = borderWidth;
}
return new DashPathEffect(TMP_FLOAT_ARRAY, 0);
}
}

View File

@ -1,44 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import android.graphics.Canvas;
/**
* DrawCommand is an interface that shadow nodes need to implement to do the drawing.
* Instances 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 abstract class DrawCommand {
// used by StateBuilder, FlatViewGroup and FlatShadowNode
/* package */ static final DrawCommand[] EMPTY_ARRAY = new DrawCommand[0];
/**
* Performs drawing into the given canvas.
*
* @param parent The parent to get child information from, if needed
* @param canvas The canvas to draw into
*/
abstract void draw(FlatViewGroup parent, Canvas canvas);
/**
* Performs debug bounds drawing into the given canvas.
*
* @param parent The parent to get child information from, if needed
* @param canvas The canvas to draw into
*/
abstract void debugDraw(FlatViewGroup parent, Canvas canvas);
abstract float getLeft();
abstract float getTop();
abstract float getRight();
abstract float getBottom();
}

View File

@ -1,156 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import javax.annotation.Nullable;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.View;
import android.view.ViewParent;
/**
* Underlying logic which handles draw commands, views and node regions when clipping in a
* {@link FlatViewGroup}.
*/
/* package */ abstract class DrawCommandManager {
/**
* Mount a set of draw commands to this manager. The order the commands are given is the order in
* which they should be drawn. If any of the commands are new DrawViews, then mountViews will be
* called after by the UIManager.
*
* @param drawCommands The draw commands to mount.
* @param drawViewIndexMap Mapping of ids to index position within the draw command array.
* @param maxBottom At each index i, the maximum bottom value (or right value in the case of
* horizontal clipping) value of all draw commands at or below i.
* @param minTop At each index i, the minimum top value (or left value in the case of horizontal
* clipping) value of all draw commands at or below i.
* @param willMountViews Whether we are going to also receive a mountViews command in this state
* cycle.
*/
abstract void mountDrawCommands(
DrawCommand[] drawCommands,
SparseIntArray drawViewIndexMap,
float[] maxBottom,
float[] minTop,
boolean willMountViews);
/**
* Add and detach a set of views. The views added here will already have a DrawView passed in
* mountDrawCommands.
*
* @param viewResolver
* @param viewsToAdd The views to add, by tag. If this is a new view, this will be reactTag,
* otherwise it will be -reactTag. This allows to optimize when we have already attached
* views.
* @param viewsToDetach The views to detach, by tag. These will all be positive.
*/
abstract void mountViews(ViewResolver viewResolver, int[] viewsToAdd, int[] viewsToDetach);
/**
* Get the current clipping rect and adjust clipping so that when draw is dispatched we do as
* little work as possible.
*
* @return true if the FlatViewGroup should invalidate.
*/
abstract boolean updateClippingRect();
/**
* Sets an input rect to match the bounds of our current clipping rect.
*
* @param outClippingRect Set the out
*/
abstract void getClippingRect(Rect outClippingRect);
/**
* Return the views that are currently detached, so they can be cleaned up when we are.
*
* @return A collection of the currently detached views.
*/
abstract SparseArray<View> getDetachedViews();
/**
* Draw the relevant items. This should do as little work as possible.
*
* @param canvas The canvas to draw on.
*/
abstract void draw(Canvas canvas);
/**
* Draws layout bounds for debug.
*
* @param canvas The canvas to draw on.
*/
abstract void debugDraw(Canvas canvas);
/**
* Mount node regions, which are the hit boxes of the shadow node children of this FlatViewGroup,
* though some may not have a corresponding draw command.
*
* @param nodeRegions Array of node regions to mount.
* @param maxBottom At each index i, the maximum bottom value (or right value in the case of
* horizontal clipping) value of all node regions at or below i.
* @param minTop At each index i, the minimum top value (or left value in the case of horizontal
* clipping) value of all draw commands at or below i.
*/
abstract void mountNodeRegions(NodeRegion[] nodeRegions, float[] maxBottom, float[] minTop);
/**
* Find a matching node region for a touch.
*
* @param touchX X coordinate of touch.
* @param touchY Y coordinate of touch.
* @return Matching node region, or null if none are found.
*/
abstract @Nullable NodeRegion anyNodeRegionWithinBounds(float touchX, float touchY);
/**
* Find a matching virtual node region for a touch.
*
* @param touchX X coordinate of touch.
* @param touchY Y coordinate of touch.
* @return Matching node region, or null if none are found.
*/
abstract @Nullable NodeRegion virtualNodeRegionWithinBounds(float touchX, float touchY);
/**
* Event that is fired when a clipped view is dropped.
*
* @param view the view that is dropped
*/
abstract void onClippedViewDropped(View view);
/**
* Throw a runtime exception if a view we are trying to attach is already parented.
*
* @param view The view to check.
*/
protected static void ensureViewHasNoParent(View view) {
ViewParent oldParent = view.getParent();
if (oldParent != null) {
throw new RuntimeException(
"Cannot add view " + view + " to DrawCommandManager while it has a parent " + oldParent);
}
}
/**
* Get a draw command manager that will clip vertically (The view scrolls up and down).
*
* @param flatViewGroup FlatViewGroup to use for drawing.
* @param drawCommands List of commands to mount.
* @return Vertically clipping draw command manager.
*/
static DrawCommandManager getVerticalClippingInstance(
FlatViewGroup flatViewGroup,
DrawCommand[] drawCommands) {
return new VerticalDrawCommandManager(flatViewGroup, drawCommands);
}
}

View File

@ -1,67 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import javax.annotation.Nullable;
import android.content.Context;
import com.facebook.drawee.drawable.ScalingUtils.ScaleType;
import com.facebook.react.bridge.ReadableArray;
/**
* Common interface for DrawImageWithDrawee.
*/
/* package */ interface DrawImage extends AttachDetachListener {
/**
* Returns true if an image source was assigned to the DrawImage.
* A DrawImage with no source will not draw anything.
*/
boolean hasImageRequest();
/**
* Assigns a new image source to the DrawImage, or null to clear the image request.
*/
void setSource(Context context, @Nullable ReadableArray sources);
/**
* Assigns a tint color to apply to the image drawn.
*/
void setTintColor(int tintColor);
/**
* Assigns a scale type to draw to the image with.
*/
void setScaleType(ScaleType scaleType);
/**
* Returns a scale type to draw to the image with.
*/
ScaleType getScaleType();
/**
* React tag used for dispatching ImageLoadEvents, or 0 to ignore events.
*/
void setReactTag(int reactTag);
void setBorderWidth(float borderWidth);
float getBorderWidth();
void setBorderRadius(float borderRadius);
float getBorderRadius();
void setBorderColor(int borderColor);
int getBorderColor();
void setFadeDuration(int fadeDuration);
void setProgressiveRenderingEnabled(boolean enabled);
}

View File

@ -1,315 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Animatable;
import android.net.Uri;
import com.facebook.drawee.controller.ControllerListener;
import com.facebook.drawee.drawable.ScalingUtils.ScaleType;
import com.facebook.drawee.generic.GenericDraweeHierarchy;
import com.facebook.drawee.generic.RoundingParams;
import com.facebook.imagepipeline.common.ResizeOptions;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.views.image.GlobalImageLoadListener;
import com.facebook.react.views.image.ImageLoadEvent;
import com.facebook.react.views.image.ImageResizeMode;
import com.facebook.react.views.image.ReactImageView;
import com.facebook.react.views.imagehelper.ImageSource;
import com.facebook.react.views.imagehelper.MultiSourceHelper;
import com.facebook.react.views.imagehelper.MultiSourceHelper.MultiSourceResult;
import java.util.LinkedList;
import java.util.List;
import javax.annotation.Nullable;
/**
* DrawImageWithDrawee is a DrawCommand that can draw a local or remote image.
* It uses DraweeRequestHelper internally to fetch and cache the images.
*/
/* package */ final class DrawImageWithDrawee extends AbstractDrawCommand
implements DrawImage, ControllerListener {
private static final String LOCAL_FILE_SCHEME = "file";
private static final String LOCAL_CONTENT_SCHEME = "content";
private final List<ImageSource> mSources = new LinkedList<>();
private final @Nullable GlobalImageLoadListener mGlobalImageLoadListener;
private @Nullable DraweeRequestHelper mRequestHelper;
private @Nullable PorterDuffColorFilter mColorFilter;
private ScaleType mScaleType = ImageResizeMode.defaultValue();
private float mBorderWidth;
private float mBorderRadius;
private int mBorderColor;
private int mReactTag;
private boolean mProgressiveRenderingEnabled;
private int mFadeDuration = ReactImageView.REMOTE_IMAGE_FADE_DURATION_MS;
private @Nullable FlatViewGroup.InvalidateCallback mCallback;
public DrawImageWithDrawee(@Nullable GlobalImageLoadListener globalImageLoadListener) {
mGlobalImageLoadListener = globalImageLoadListener;
}
@Override
public boolean hasImageRequest() {
return !mSources.isEmpty();
}
@Override
public void setSource(Context context, @Nullable ReadableArray sources) {
mSources.clear();
if (sources != null && sources.size() != 0) {
// Optimize for the case where we have just one uri, case in which we don't need the sizes
if (sources.size() == 1) {
ReadableMap source = sources.getMap(0);
mSources.add(new ImageSource(context, source.getString("uri")));
} else {
for (int idx = 0; idx < sources.size(); idx++) {
ReadableMap source = sources.getMap(idx);
mSources.add(new ImageSource(
context,
source.getString("uri"),
source.getDouble("width"),
source.getDouble("height")));
}
}
}
}
@Override
public void setTintColor(int tintColor) {
if (tintColor == 0) {
mColorFilter = null;
} else {
mColorFilter = new PorterDuffColorFilter(tintColor, PorterDuff.Mode.SRC_ATOP);
}
}
@Override
public void setScaleType(ScaleType scaleType) {
mScaleType = scaleType;
}
@Override
public ScaleType getScaleType() {
return mScaleType;
}
@Override
public void setBorderWidth(float borderWidth) {
mBorderWidth = borderWidth;
}
@Override
public float getBorderWidth() {
return mBorderWidth;
}
@Override
public void setBorderRadius(float borderRadius) {
mBorderRadius = borderRadius;
}
@Override
public float getBorderRadius() {
return mBorderRadius;
}
@Override
public void setBorderColor(int borderColor) {
mBorderColor = borderColor;
}
@Override
public int getBorderColor() {
return mBorderColor;
}
@Override
public void setFadeDuration(int fadeDuration) {
mFadeDuration = fadeDuration;
}
@Override
public void setProgressiveRenderingEnabled(boolean enabled) {
mProgressiveRenderingEnabled = enabled;
}
@Override
public void setReactTag(int reactTag) {
mReactTag = reactTag;
}
@Override
public void onDraw(Canvas canvas) {
if (mRequestHelper != null) {
mRequestHelper.getDrawable().draw(canvas);
}
}
@Override
public void onAttached(FlatViewGroup.InvalidateCallback callback) {
mCallback = callback;
if (mRequestHelper == null) {
// this is here to help us debug t12048319, in which we have a null request helper on attach
throw new RuntimeException(
"No DraweeRequestHelper - width: " +
(getRight() - getLeft()) +
" - height: " + (getBottom() - getTop() +
" - number of sources: " + mSources.size()));
}
GenericDraweeHierarchy hierarchy = mRequestHelper.getHierarchy();
RoundingParams roundingParams = hierarchy.getRoundingParams();
if (shouldDisplayBorder()) {
if (roundingParams == null) {
roundingParams = new RoundingParams();
}
roundingParams.setBorder(mBorderColor, mBorderWidth);
roundingParams.setCornersRadius(mBorderRadius);
// changes won't take effect until we re-apply rounding params, so do it now.
hierarchy.setRoundingParams(roundingParams);
} else if (roundingParams != null) {
// clear rounding params
hierarchy.setRoundingParams(null);
}
hierarchy.setActualImageScaleType(mScaleType);
hierarchy.setActualImageColorFilter(mColorFilter);
hierarchy.setFadeDuration(mFadeDuration);
hierarchy.getTopLevelDrawable().setBounds(
Math.round(getLeft()),
Math.round(getTop()),
Math.round(getRight()),
Math.round(getBottom()));
mRequestHelper.attach(callback);
}
@Override
public void onDetached() {
if (mRequestHelper != null) {
mRequestHelper.detach();
}
}
@Override
public void onSubmit(String id, Object callerContext) {
if (mCallback != null && mReactTag != 0) {
mCallback.dispatchImageLoadEvent(mReactTag, ImageLoadEvent.ON_LOAD_START);
}
}
@Override
public void onFinalImageSet(
String id,
@Nullable Object imageInfo,
@Nullable Animatable animatable) {
if (mCallback != null && mReactTag != 0) {
mCallback.dispatchImageLoadEvent(mReactTag, ImageLoadEvent.ON_LOAD);
mCallback.dispatchImageLoadEvent(mReactTag, ImageLoadEvent.ON_LOAD_END);
}
}
@Override
public void onIntermediateImageSet(String id, @Nullable Object imageInfo) {
}
@Override
public void onIntermediateImageFailed(String id, Throwable throwable) {
}
@Override
public void onFailure(String id, Throwable throwable) {
if (mCallback != null && mReactTag != 0) {
mCallback.dispatchImageLoadEvent(mReactTag, ImageLoadEvent.ON_ERROR);
mCallback.dispatchImageLoadEvent(mReactTag, ImageLoadEvent.ON_LOAD_END);
}
}
@Override
public void onRelease(String id) {
}
@Override
protected void onBoundsChanged() {
super.onBoundsChanged();
computeRequestHelper();
}
private void computeRequestHelper() {
MultiSourceResult multiSource = MultiSourceHelper.getBestSourceForSize(
Math.round(getRight() - getLeft()),
Math.round(getBottom() - getTop()),
mSources);
ImageSource source = multiSource.getBestResult();
ImageSource cachedSource = multiSource.getBestResultInCache();
if (source == null) {
mRequestHelper = null;
return;
}
ResizeOptions resizeOptions = null;
if (shouldResize(source)) {
final int width = (int) (getRight() - getLeft());
final int height = (int) (getBottom() - getTop());
resizeOptions = new ResizeOptions(width, height);
}
ImageRequest imageRequest = ImageRequestBuilder.newBuilderWithSource(source.getUri())
.setResizeOptions(resizeOptions)
.setProgressiveRenderingEnabled(mProgressiveRenderingEnabled)
.build();
if (mGlobalImageLoadListener != null) {
mGlobalImageLoadListener.onLoadAttempt(source.getUri());
}
ImageRequest cachedImageRequest = null;
if (cachedSource != null) {
cachedImageRequest = ImageRequestBuilder.newBuilderWithSource(cachedSource.getUri())
.setResizeOptions(resizeOptions)
.setProgressiveRenderingEnabled(mProgressiveRenderingEnabled)
.build();
}
mRequestHelper = new
DraweeRequestHelper(Assertions.assertNotNull(imageRequest), cachedImageRequest, this);
}
private boolean shouldDisplayBorder() {
return mBorderColor != 0 || mBorderRadius >= 0.5f;
}
private static boolean shouldResize(ImageSource imageSource) {
// Resizing is inferior to scaling. See http://frescolib.org/docs/resizing-rotating.html
// We resize here only for images likely to be from the device's camera, where the app developer
// has no control over the original size
Uri uri = imageSource.getUri();
String type = uri == null ? null : uri.getScheme();
// one day, we can replace this with what non-Nodes does, which is:
// UriUtil.isLocalContentUri || UriUtil.isLocalFileUri
// not doing this just to save including eyt another BUCK dependency
return LOCAL_FILE_SCHEME.equals(type) || LOCAL_CONTENT_SCHEME.equals(type);
}
@Override
protected void onDebugDrawHighlight(Canvas canvas) {
if (mCallback != null) {
debugDrawCautionHighlight(canvas, "Invalidate Drawee");
}
}
}

View File

@ -1,59 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import android.graphics.Canvas;
import android.text.Layout;
import com.facebook.fbui.textlayoutbuilder.util.LayoutMeasureUtil;
/**
* DrawTextLayout is a DrawCommand that draw {@link Layout}.
*/
/* package */ final class DrawTextLayout extends AbstractDrawCommand {
private Layout mLayout;
private float mLayoutWidth;
private float mLayoutHeight;
/* package */ DrawTextLayout(Layout layout) {
setLayout(layout);
}
/**
* Assigns a new {@link Layout} to draw.
*/
public void setLayout(Layout layout) {
mLayout = layout;
mLayoutWidth = layout.getWidth();
mLayoutHeight = LayoutMeasureUtil.getHeight(layout);
}
public Layout getLayout() {
return mLayout;
}
public float getLayoutWidth() {
// mLayout.getWidth() doesn't return correct width of the text Layout
return mLayoutWidth;
}
public float getLayoutHeight() {
return mLayoutHeight;
}
@Override
protected void onDraw(Canvas canvas) {
float left = getLeft();
float top = getTop();
canvas.translate(left, top);
mLayout.draw(canvas);
canvas.translate(-left, -top);
}
}

View File

@ -1,233 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import javax.annotation.Nullable;
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.RectF;
/* package */ final class DrawView extends AbstractDrawCommand {
public static final DrawView[] EMPTY_ARRAY = new DrawView[0];
// the minimum rounded clipping value before we actually do rounded clipping
/* package */ static final float MINIMUM_ROUNDED_CLIPPING_VALUE = 0.5f;
private final RectF TMP_RECT = new RectF();
/* package */ final int reactTag;
// Indicates whether this DrawView has been previously mounted to a clipping FlatViewGroup. This
// lets us know that the bounds haven't changed, as a bounds change would trigger a new DrawView,
// which will set this to false for the new DrawView. This is safe, despite the dual access with
// FlatViewGroup, because the FlatViewGroup copy is only ever modified by the FlatViewGroup.
// Changing how this boolean is used should be handled with caution, as race conditions are the
// quickest way to create unreproducible super bugs.
/* package */ boolean mWasMounted;
// the clipping radius - if this is greater than MINIMUM_ROUNDED_CLIPPING_VALUE, we clip using
// a rounded path, otherwise we clip in a rectangular fashion.
private float mClipRadius;
// the path to clip against if we're doing path clipping for rounded borders.
@Nullable private Path mPath;
// These should only ever be set from within the DrawView, they serve to provide clipping bounds
// for FlatViewGroups, which have strange clipping when it comes to overflow: visible. They are
// left package protected to speed up direct access. For overflow visible, these are the adjusted
// bounds while taking overflowing elements into account, other wise they are just the regular
// bounds of the view.
/* package */ float mLogicalLeft;
/* package */ float mLogicalTop;
/* package */ float mLogicalRight;
/* package */ float mLogicalBottom;
public DrawView(int reactTag) {
this.reactTag = reactTag;
}
/**
* Similar to updateBoundsAndFreeze, but thread safe as the mounting flag is modified on the UI
* thread.
*
* @return A DrawView with the passed bounds and clipping bounds. If we can use the same
* DrawView, it will just be this, otherwise it will be a frozen copy.
*/
public DrawView collectDrawView(
float left,
float top,
float right,
float bottom,
float logicalLeft,
float logicalTop,
float logicalRight,
float logicalBottom,
float clipLeft,
float clipTop,
float clipRight,
float clipBottom,
float clipRadius) {
if (!isFrozen()) {
// We haven't collected this draw view yet, so we can just set everything.
setBounds(left, top, right, bottom);
setClipBounds(clipLeft, clipTop, clipRight, clipBottom);
setClipRadius(clipRadius);
setLogicalBounds(logicalLeft, logicalTop, logicalRight, logicalBottom);
freeze();
return this;
}
boolean boundsMatch = boundsMatch(left, top, right, bottom);
boolean clipBoundsMatch = clipBoundsMatch(clipLeft, clipTop, clipRight, clipBottom);
boolean clipRadiusMatch = mClipRadius == clipRadius;
boolean logicalBoundsMatch =
logicalBoundsMatch(logicalLeft, logicalTop, logicalRight, logicalBottom);
// See if we can reuse the draw view.
if (boundsMatch && clipBoundsMatch && clipRadiusMatch && logicalBoundsMatch) {
return this;
}
DrawView drawView = (DrawView) mutableCopy();
if (!boundsMatch) {
drawView.setBounds(left, top, right, bottom);
}
if (!clipBoundsMatch) {
drawView.setClipBounds(clipLeft, clipTop, clipRight, clipBottom);
}
if (!logicalBoundsMatch) {
drawView.setLogicalBounds(logicalLeft, logicalTop, logicalRight, logicalBottom);
}
if (!clipRadiusMatch || !boundsMatch) {
// If the bounds change, we need to update the clip path.
drawView.setClipRadius(clipRadius);
}
// It is very important that we unset this, as our spec is that newly created DrawViews
// are handled differently by the FlatViewGroup. This is needed because clone() maintains
// the previous state.
drawView.mWasMounted = false;
drawView.freeze();
return drawView;
}
private boolean logicalBoundsMatch(float left, float top, float right, float bottom) {
return left == mLogicalLeft && top == mLogicalTop &&
right == mLogicalRight && bottom == mLogicalBottom;
}
private void setLogicalBounds(float left, float top, float right, float bottom) {
// Do rounding up front and off of the UI thread.
mLogicalLeft = left;
mLogicalTop = top;
mLogicalRight = right;
mLogicalBottom = bottom;
}
@Override
public void draw(FlatViewGroup parent, Canvas canvas) {
onPreDraw(parent, canvas);
if (mNeedsClipping || mClipRadius > MINIMUM_ROUNDED_CLIPPING_VALUE) {
canvas.save(Canvas.CLIP_SAVE_FLAG);
applyClipping(canvas);
parent.drawNextChild(canvas);
canvas.restore();
} else {
parent.drawNextChild(canvas);
}
}
/**
* Set the clip radius. Should only be called when the clip radius is first set or when it
* changes, in order to avoid extra work.
*
* @param clipRadius The new clip radius.
*/
void setClipRadius(float clipRadius) {
mClipRadius = clipRadius;
if (clipRadius > MINIMUM_ROUNDED_CLIPPING_VALUE) {
// update the path that we'll clip based on
updateClipPath();
} else {
mPath = null;
}
}
/**
* Update the path with which we'll clip this view
*/
private void updateClipPath() {
mPath = new Path();
TMP_RECT.set(
getLeft(),
getTop(),
getRight(),
getBottom());
// set the path
mPath.addRoundRect(
TMP_RECT,
mClipRadius,
mClipRadius,
Path.Direction.CW);
}
@Override
protected void applyClipping(Canvas canvas) {
// only clip using a path if our radius is greater than some minimum threshold, because
// clipPath is more expensive than clipRect.
if (mClipRadius > MINIMUM_ROUNDED_CLIPPING_VALUE) {
canvas.clipPath(mPath);
} else {
super.applyClipping(canvas);
}
}
@Override
protected void onDraw(Canvas canvas) {
// no op as we override draw.
}
@Override
protected void onDebugDraw(FlatViewGroup parent, Canvas canvas) {
parent.debugDrawNextChild(canvas);
}
@Override
protected void onDebugDrawHighlight(Canvas canvas) {
if (mPath != null) {
debugDrawWarningHighlight(canvas, "borderRadius: " + mClipRadius);
} else if (!boundsMatch(mLogicalLeft, mLogicalTop, mLogicalRight, mLogicalBottom)) {
StringBuilder warn = new StringBuilder("Overflow: { ");
String[] names = { "left: ", "top: ", "right: ", "bottom: "};
int i = 0;
float[] offsets = new float[4];
offsets[i++] = getLeft() - mLogicalLeft;
offsets[i++] = getTop() - mLogicalTop;
offsets[i++] = mLogicalRight - getRight();
offsets[i++] = mLogicalBottom - getBottom();
for (i = 0; i < 4; i++) {
if (offsets[i] != 0f) {
warn.append(names[i]);
warn.append(offsets[i]);
warn.append(", ");
}
}
warn.append("}");
debugDrawCautionHighlight(canvas, warn.toString());
}
}
}

View File

@ -1,80 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import javax.annotation.Nullable;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import com.facebook.drawee.controller.AbstractDraweeControllerBuilder;
import com.facebook.drawee.controller.ControllerListener;
import com.facebook.drawee.generic.GenericDraweeHierarchy;
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
import com.facebook.drawee.interfaces.DraweeController;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.infer.annotation.Assertions;
/* package */ final class DraweeRequestHelper {
private static GenericDraweeHierarchyBuilder sHierarchyBuilder;
private static AbstractDraweeControllerBuilder sControllerBuilder;
/* package */ static void setResources(Resources resources) {
sHierarchyBuilder = new GenericDraweeHierarchyBuilder(resources);
}
/* package */ static void setDraweeControllerBuilder(AbstractDraweeControllerBuilder builder) {
sControllerBuilder = builder;
}
private final DraweeController mDraweeController;
private int mAttachCounter;
/* package */ DraweeRequestHelper(
ImageRequest imageRequest,
@Nullable ImageRequest cachedImageRequest,
ControllerListener listener) {
AbstractDraweeControllerBuilder controllerBuilder = sControllerBuilder
.setImageRequest(imageRequest)
.setCallerContext(RCTImageView.getCallerContext())
.setControllerListener(listener);
if (cachedImageRequest != null) {
controllerBuilder.setLowResImageRequest(cachedImageRequest);
}
DraweeController controller = controllerBuilder.build();
controller.setHierarchy(sHierarchyBuilder.build());
mDraweeController = controller;
}
/* package */ void attach(FlatViewGroup.InvalidateCallback callback) {
++mAttachCounter;
if (mAttachCounter == 1) {
getDrawable().setCallback(callback.get());
mDraweeController.onAttach();
}
}
/* package */ void detach() {
--mAttachCounter;
if (mAttachCounter == 0) {
mDraweeController.onDetach();
}
}
/* package */ GenericDraweeHierarchy getHierarchy() {
return (GenericDraweeHierarchy) Assertions.assumeNotNull(mDraweeController.getHierarchy());
}
/* package */ Drawable getDrawable() {
return getHierarchy().getTopLevelDrawable();
}
}

View File

@ -1,198 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.lang.reflect.Array;
/**
* Diffing scope stack class that supports 3 main operations: start(), add() an element and
* finish().
*
* When started, it takes a baseline array to compare to. When adding a new element, it checks
* whether a corresponding element in baseline array is the same. On finish(), it will return null
* if baseline array contains exactly the same elements that were added with a sequence of add()
* calls, or a new array of the recorded elements:
*
* Example 1:
* -----
* start([A])
* add(A)
* finish() -> null (because [A] == [A])
*
* Example 2:
* ----
* start([A])
* add(B)
* finish() -> [B] (because [A] != [B])
*
* Example 3:
* ----
* start([A])
* add(B)
* add(A)
* finish() -> [B, A] (because [B, A] != [A])
*
* Example 4:
* ----
* start([A, B])
* add(B)
* add(A)
* finish() -> [B, A] (because [B, A] != [A, B])
*
* It is important that start/finish can be nested:
* ----
* start([A])
* add(A)
* start([B])
* add(B)
* finish() -> null
* add(C)
* finish() -> [A, C]
*
* StateBuilder is using this class to check if e.g. a DrawCommand list for a given View needs to be
* updated.
*/
/* package */ final class ElementsList<E> {
private static final class Scope {
Object[] elements;
int index;
int size;
}
// List of scopes. These are never cleared, but instead recycled when a new scope is needed at
// a given depth.
private final ArrayList<Scope> mScopesStack = new ArrayList<>();
// Working list of all new elements we are gathering across scopes. Whenever we get a call to
// finish() we pop the new elements off the collection, either discarding them if there was no
// change from the base or accumulating and returning them as a list of new elements.
private final ArrayDeque<E> mElements = new ArrayDeque<>();
private final E[] mEmptyArray;
private Scope mCurrentScope = null;
private int mScopeIndex = 0;
public ElementsList(E[] emptyArray) {
mEmptyArray = emptyArray;
mScopesStack.add(mCurrentScope);
}
/**
* Starts a new scope.
*/
public void start(Object[] elements) {
pushScope();
Scope scope = getCurrentScope();
scope.elements = elements;
scope.index = 0;
scope.size = mElements.size();
}
/**
* Finish current scope, returning null if there were no changes recorded, or a new array
* containing all the newly recorded elements otherwise.
*/
public E[] finish() {
Scope scope = getCurrentScope();
popScope();
E[] result = null;
int size = mElements.size() - scope.size;
if (scope.index != scope.elements.length) {
result = extractElements(size);
} else {
// downsize
for (int i = 0; i < size; ++i) {
mElements.pollLast();
}
}
// To prevent resource leaks.
scope.elements = null;
return result;
}
/**
* Adds a new element to the list. This method can be optimized to avoid inserts on same
* elements, but would involve copying from scope.elements when we extract elements.
*/
public void add(E element) {
Scope scope = getCurrentScope();
if (scope.index < scope.elements.length &&
scope.elements[scope.index] == element) {
++scope.index;
} else {
scope.index = Integer.MAX_VALUE;
}
mElements.add(element);
}
/**
* Resets all references to elements in our new stack to null to avoid memory leaks.
*/
public void clear() {
if (getCurrentScope() != null) {
throw new RuntimeException("Must call finish() for every start() call being made.");
}
mElements.clear();
}
/**
* Extracts last size elements into an array. Used to extract our new array of items from our
* stack when the new items != old items.
*/
private E[] extractElements(int size) {
if (size == 0) {
// avoid allocating empty array
return mEmptyArray;
}
E[] elements = (E[]) Array.newInstance(mEmptyArray.getClass().getComponentType(), size);
for (int i = size - 1; i >= 0; --i) {
elements[i] = mElements.pollLast();
}
return elements;
}
/**
* Saves current scope in a stack.
*/
private void pushScope() {
++mScopeIndex;
if (mScopeIndex == mScopesStack.size()) {
// We reached a new deepest scope, we need to create a scope for this depth.
mCurrentScope = new Scope();
mScopesStack.add(mCurrentScope);
} else {
// We have had a scope at this depth before, lets recycle it.
mCurrentScope = mScopesStack.get(mScopeIndex);
}
}
/**
* Restores last saved current scope. Doesn't actually remove the scope, as scopes are
* recycled.
*/
private void popScope() {
--mScopeIndex;
mCurrentScope = mScopesStack.get(mScopeIndex);
}
/**
* Returns current scope.
*/
private Scope getCurrentScope() {
return mCurrentScope;
}
}

View File

@ -1,60 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import com.facebook.react.uimanager.BaseViewManager;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.views.art.ARTSurfaceView;
import com.facebook.yoga.YogaMeasureFunction;
import com.facebook.yoga.YogaMeasureMode;
import com.facebook.yoga.YogaNode;
public class FlatARTSurfaceViewManager extends
BaseViewManager<ARTSurfaceView, FlatARTSurfaceViewShadowNode> {
/* package */ static final String REACT_CLASS = "ARTSurfaceView";
private static final YogaMeasureFunction MEASURE_FUNCTION = new YogaMeasureFunction() {
@Override
public long measure(
YogaNode node,
float width,
YogaMeasureMode widthMode,
float height,
YogaMeasureMode heightMode) {
throw new IllegalStateException("SurfaceView should have explicit width and height set");
}
};
@Override
public String getName() {
return REACT_CLASS;
}
@Override
public FlatARTSurfaceViewShadowNode createShadowNodeInstance() {
FlatARTSurfaceViewShadowNode node = new FlatARTSurfaceViewShadowNode();
node.setMeasureFunction(MEASURE_FUNCTION);
return node;
}
@Override
public Class<FlatARTSurfaceViewShadowNode> getShadowNodeClass() {
return FlatARTSurfaceViewShadowNode.class;
}
@Override
protected ARTSurfaceView createViewInstance(ThemedReactContext reactContext) {
return new ARTSurfaceView(reactContext);
}
@Override
public void updateExtraData(ARTSurfaceView root, Object extraData) {
root.setSurfaceTextureListener((FlatARTSurfaceViewShadowNode) extraData);
}
}

View File

@ -1,143 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import javax.annotation.Nullable;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.SurfaceTexture;
import android.util.Log;
import android.view.Surface;
import android.view.TextureView;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.uimanager.ReactShadowNode;
import com.facebook.react.uimanager.UIViewOperationQueue;
import com.facebook.react.views.art.ARTVirtualNode;
import com.facebook.yoga.YogaValue;
import com.facebook.yoga.YogaUnit;
/* package */ class FlatARTSurfaceViewShadowNode extends FlatShadowNode
implements AndroidView, TextureView.SurfaceTextureListener {
private boolean mPaddingChanged = false;
private @Nullable Surface mSurface;
/* package */ FlatARTSurfaceViewShadowNode() {
forceMountToView();
forceMountChildrenToView();
}
@Override
public boolean isVirtual() {
return false;
}
@Override
public boolean isVirtualAnchor() {
return true;
}
@Override
public void onCollectExtraUpdates(UIViewOperationQueue uiUpdater) {
super.onCollectExtraUpdates(uiUpdater);
drawOutput();
uiUpdater.enqueueUpdateExtraData(getReactTag(), this);
}
private void drawOutput() {
if (mSurface == null || !mSurface.isValid()) {
markChildrenUpdatesSeen(this);
return;
}
try {
Canvas canvas = mSurface.lockCanvas(null);
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
Paint paint = new Paint();
for (int i = 0; i < getChildCount(); i++) {
ARTVirtualNode child = (ARTVirtualNode) getChildAt(i);
child.draw(canvas, paint, 1f);
child.markUpdateSeen();
}
if (mSurface == null) {
return;
}
mSurface.unlockCanvasAndPost(canvas);
} catch (IllegalArgumentException | IllegalStateException e) {
Log.e(ReactConstants.TAG, e.getClass().getSimpleName() + " in Surface.unlockCanvasAndPost");
}
}
private void markChildrenUpdatesSeen(ReactShadowNode shadowNode) {
for (int i = 0; i < shadowNode.getChildCount(); i++) {
ReactShadowNode child = shadowNode.getChildAt(i);
child.markUpdateSeen();
markChildrenUpdatesSeen(child);
}
}
@Override
public boolean needsCustomLayoutForChildren() {
return false;
}
@Override
public boolean isPaddingChanged() {
return mPaddingChanged;
}
@Override
public void resetPaddingChanged() {
mPaddingChanged = false;
}
@Override
public void setPadding(int spacingType, float padding) {
YogaValue current = getStylePadding(spacingType);
if (current.unit != YogaUnit.POINT || current.value != padding) {
super.setPadding(spacingType, padding);
mPaddingChanged = true;
markUpdated();
}
}
@Override
public void setPaddingPercent(int spacingType, float percent) {
YogaValue current = getStylePadding(spacingType);
if (current.unit != YogaUnit.PERCENT || current.value != percent) {
super.setPadding(spacingType, percent);
mPaddingChanged = true;
markUpdated();
}
}
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
mSurface = new Surface(surface);
drawOutput();
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
surface.release();
mSurface = null;
return true;
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {}
}

View File

@ -1,24 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import android.graphics.Rect;
/**
* Helper interface to provide measuring of FlatViewGroup when needed. We don't override onMeasure
* for FlatViewGroup, which means that draw commands don't contributed to the measured width and
* height. This allows us to expose our calculated dimensions taking into account draw commands,
* without changing the visibility of the FlatViewGroup.
*/
public interface FlatMeasuredViewGroup {
/**
* @return A rect consisting of the left, top, right, and bottommost edge among all children,
* including draw commands.
*/
Rect measureWithCommands();
}

View File

@ -1,261 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
import com.facebook.react.uimanager.NativeViewHierarchyManager;
import com.facebook.react.uimanager.common.SizeMonitoringFrameLayout;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
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
implements ViewResolver {
/* package */ FlatNativeViewHierarchyManager(ViewManagerRegistry viewManagers) {
super(viewManagers, new FlatRootViewManager());
}
@Override
public View getView(int reactTag) {
return super.resolveView(reactTag);
}
@Override
public void addRootView(
int tag,
SizeMonitoringFrameLayout view,
ThemedReactContext themedContext) {
FlatViewGroup root = new FlatViewGroup(themedContext);
view.addView(root);
// When unmounting, ReactInstanceManager.detachViewFromInstance() will check id of the
// top-level View (SizeMonitoringFrameLayout) and pass it back to JS. We want that View's id to
// be set, otherwise NativeViewHierarchyManager will not be able to cleanup properly.
view.setId(tag);
addRootViewGroup(tag, root, themedContext);
}
/**
* Updates DrawCommands and AttachDetachListeners of a FlatViewGroup specified by a reactTag.
*
* @param reactTag reactTag to lookup FlatViewGroup by
* @param drawCommands if non-null, new draw commands to execute during the drawing.
* @param listeners if non-null, new attach-detach listeners.
*/
/* package */ void updateMountState(
int reactTag,
@Nullable DrawCommand[] drawCommands,
@Nullable AttachDetachListener[] listeners,
@Nullable NodeRegion[] nodeRegions) {
FlatViewGroup view = (FlatViewGroup) resolveView(reactTag);
if (drawCommands != null) {
view.mountDrawCommands(drawCommands);
}
if (listeners != null) {
view.mountAttachDetachListeners(listeners);
}
if (nodeRegions != null) {
view.mountNodeRegions(nodeRegions);
}
}
/**
* Updates DrawCommands and AttachDetachListeners of a clipping FlatViewGroup specified by a
* reactTag.
*
* @param reactTag The react tag to lookup FlatViewGroup by.
* @param drawCommands If non-null, new draw commands to execute during the drawing.
* @param drawViewIndexMap Mapping of react tags to the index of the corresponding DrawView
* command in the draw command array.
* @param commandMaxBot At each index i, the maximum bottom value (or right value in the case of
* horizontal clipping) value of all draw commands at or below i.
* @param commandMinTop At each index i, the minimum top value (or left value in the case of
* horizontal clipping) value of all draw commands at or below i.
* @param listeners If non-null, new attach-detach listeners.
* @param nodeRegions Node regions to mount.
* @param regionMaxBot At each index i, the maximum bottom value (or right value in the case of
* horizontal clipping) value of all node regions at or below i.
* @param regionMinTop At each index i, the minimum top value (or left value in the case of
* horizontal clipping) value of all draw commands at or below i.
* @param willMountViews Whether we are going to also send a mountViews command in this state
* cycle.
*/
/* package */ void updateClippingMountState(
int reactTag,
@Nullable DrawCommand[] drawCommands,
SparseIntArray drawViewIndexMap,
float[] commandMaxBot,
float[] commandMinTop,
@Nullable AttachDetachListener[] listeners,
@Nullable NodeRegion[] nodeRegions,
float[] regionMaxBot,
float[] regionMinTop,
boolean willMountViews) {
FlatViewGroup view = (FlatViewGroup) resolveView(reactTag);
if (drawCommands != null) {
view.mountClippingDrawCommands(
drawCommands,
drawViewIndexMap,
commandMaxBot,
commandMinTop,
willMountViews);
}
if (listeners != null) {
view.mountAttachDetachListeners(listeners);
}
if (nodeRegions != null) {
view.mountClippingNodeRegions(nodeRegions, regionMaxBot, regionMinTop);
}
}
/* package */ void updateViewGroup(int reactTag, int[] viewsToAdd, int[] viewsToDetach) {
View view = resolveView(reactTag);
if (view instanceof FlatViewGroup) {
((FlatViewGroup) view).mountViews(this, viewsToAdd, viewsToDetach);
return;
}
ViewGroup viewGroup = (ViewGroup) view;
ViewGroupManager viewManager = (ViewGroupManager) resolveViewManager(reactTag);
List<View> listOfViews = new ArrayList<>(viewsToAdd.length);
// batch the set of additions - some view managers can take advantage of the batching to
// decrease operations, etc.
for (int viewIdToAdd : viewsToAdd) {
int tag = Math.abs(viewIdToAdd);
listOfViews.add(resolveView(tag));
}
viewManager.addViews(viewGroup, listOfViews);
}
/**
* 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());
}
}
/* package */ void setPadding(
int reactTag,
int paddingLeft,
int paddingTop,
int paddingRight,
int paddingBottom) {
resolveView(reactTag).setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);
}
/* package */ void dropViews(SparseIntArray viewsToDrop) {
for (int i = 0, count = viewsToDrop.size(); i < count; i++) {
int viewToDrop = viewsToDrop.keyAt(i);
View view = null;
if (viewToDrop > 0) {
try {
view = resolveView(viewToDrop);
dropView(view);
} catch (Exception e) {
// the view is already dropped, nothing we can do
}
} else {
// Root views are noted with a negative tag from StateBuilder.
removeRootView(-viewToDrop);
}
int parentTag = viewsToDrop.valueAt(i);
// this only happens for clipped, non-root views - clipped because there is no parent, and
// not a root view (because we explicitly pass -1 for root views).
if (parentTag > 0 && view != null && view.getParent() == null) {
// this can only happen if the parent exists (if the parent were removed first, it'd also
// remove the child, so trying to explicitly remove the child afterwards would crash at
// the resolveView call above) - we also explicitly check for a null parent, implying that
// we are either clipped (or that we already removed the child from its parent, in which
// case this will essentially be a no-op).
View parent = resolveView(parentTag);
if (parent instanceof FlatViewGroup) {
((FlatViewGroup) parent).onViewDropped(view);
}
}
}
}
@Override
protected void dropView(View view) {
super.dropView(view);
// As a result of removeClippedSubviews, some views have strong references but are not attached
// to a parent. consequently, when the parent gets removed, these Views don't get cleaned up,
// because they aren't children (they also aren't removed from mTagsToViews, thus causing a
// leak). To solve this, we ask for said detached views and explicitly drop them.
if (view instanceof FlatViewGroup) {
FlatViewGroup flatViewGroup = (FlatViewGroup) view;
if (flatViewGroup.getRemoveClippedSubviews()) {
SparseArray<View> detachedViews = flatViewGroup.getDetachedViews();
for (int i = 0, size = detachedViews.size(); i < size; i++) {
View detachedChild = detachedViews.valueAt(i);
try {
dropView(detachedChild);
} catch (Exception e) {
// if the view is already dropped, ignore any exceptions
// in reality, we should find out the edge cases that cause
// this to happen and properly fix them.
}
// trigger onDetachedFromWindow and clean up this detached/clipped view
flatViewGroup.removeDetachedView(detachedChild);
}
}
}
}
/* package */ void detachAllChildrenFromViews(int[] viewsToDetachAllChildrenFrom) {
for (int viewTag : viewsToDetachAllChildrenFrom) {
View view = resolveView(viewTag);
if (view instanceof FlatViewGroup) {
((FlatViewGroup) view).detachAllViewsFromParent();
continue;
}
ViewGroup viewGroup = (ViewGroup) view;
ViewGroupManager viewManager = (ViewGroupManager) resolveViewManager(viewTag);
viewManager.removeAllViews(viewGroup);
}
}
}

View File

@ -1,105 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Point;
import android.view.Display;
import android.view.Surface;
import android.view.WindowManager;
import com.facebook.react.uimanager.ReactShadowNodeImpl;
import com.facebook.yoga.YogaUnit;
import com.facebook.yoga.YogaValue;
/**
* FlatReactModalShadowNode
*
* This is a Nodes specific shadow node for modals. This is required because we wrap any
* non-FlatShadowNode node in a NativeViewWrapper. In the case of a modal shadow node, we need
* to treat it as its own node so that we can add the custom measurement behavior that is there
* in the non-Nodes version when we add a child.
*
* {@see {@link com.facebook.react.views.modal.ModalHostShadowNode}}
*/
class FlatReactModalShadowNode extends FlatShadowNode implements AndroidView {
private final Point mMinPoint = new Point();
private final Point mMaxPoint = new Point();
private boolean mPaddingChanged;
FlatReactModalShadowNode() {
forceMountToView();
forceMountChildrenToView();
}
/**
* We need to set the styleWidth and styleHeight of the one child (represented by the <View/>
* within the <RCTModalHostView/> in Modal.js. This needs to fill the entire window.
*/
@Override
@TargetApi(16)
public void addChildAt(ReactShadowNodeImpl child, int i) {
super.addChildAt(child, i);
Context context = getThemedContext();
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
// getCurrentSizeRange will return the min and max width and height that the window can be
display.getCurrentSizeRange(mMinPoint, mMaxPoint);
int width, height;
int rotation = display.getRotation();
if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
// If we are vertical the width value comes from min width and height comes from max height
width = mMinPoint.x;
height = mMaxPoint.y;
} else {
// If we are horizontal the width value comes from max width and height comes from min height
width = mMaxPoint.x;
height = mMinPoint.y;
}
child.setStyleWidth(width);
child.setStyleHeight(height);
}
@Override
public boolean needsCustomLayoutForChildren() {
return false;
}
@Override
public boolean isPaddingChanged() {
return mPaddingChanged;
}
@Override
public void resetPaddingChanged() {
mPaddingChanged = false;
}
@Override
public void setPadding(int spacingType, float padding) {
YogaValue current = getStylePadding(spacingType);
if (current.unit != YogaUnit.POINT || current.value != padding) {
super.setPadding(spacingType, padding);
mPaddingChanged = true;
markUpdated();
}
}
@Override
public void setPaddingPercent(int spacingType, float percent) {
YogaValue current = getStylePadding(spacingType);
if (current.unit != YogaUnit.PERCENT || current.value != percent) {
super.setPadding(spacingType, percent);
mPaddingChanged = true;
markUpdated();
}
}
}

View File

@ -1,19 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
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 {
/* package */ FlatRootShadowNode() {
forceMountToView();
signalBackingViewIsCreated();
}
}

View File

@ -1,20 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import android.view.ViewGroup;
import com.facebook.react.uimanager.RootViewManager;
/* package */ class FlatRootViewManager extends RootViewManager {
@Override
public void removeAllViews(ViewGroup parent) {
parent.removeAllViewsInLayout();
}
}

View File

@ -1,570 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import android.graphics.Rect;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.uimanager.LayoutShadowNode;
import com.facebook.react.uimanager.OnLayoutEvent;
import com.facebook.react.uimanager.ReactClippingViewGroupHelper;
import com.facebook.react.uimanager.ReactShadowNode;
import com.facebook.react.uimanager.ReactShadowNodeImpl;
import com.facebook.react.uimanager.ReactStylesDiffMap;
import com.facebook.react.uimanager.ViewProps;
import com.facebook.react.uimanager.annotations.ReactProp;
import javax.annotation.Nullable;
/**
* 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 {
/* package */ static final FlatShadowNode[] EMPTY_ARRAY = new FlatShadowNode[0];
private static final String PROP_OPACITY = "opacity";
private static final String PROP_RENDER_TO_HARDWARE_TEXTURE = "renderToHardwareTextureAndroid";
private static final String PROP_ACCESSIBILITY_LABEL = "accessibilityLabel";
private static final String PROP_ACCESSIBILITY_COMPONENT_TYPE = "accessibilityComponentType";
private static final String PROP_ACCESSIBILITY_LIVE_REGION = "accessibilityLiveRegion";
private static final String PROP_IMPORTANT_FOR_ACCESSIBILITY = "importantForAccessibility";
private static final String PROP_TEST_ID = "testID";
private static final String PROP_TRANSFORM = "transform";
protected static final String PROP_REMOVE_CLIPPED_SUBVIEWS =
ReactClippingViewGroupHelper.PROP_REMOVE_CLIPPED_SUBVIEWS;
protected static final String PROP_HORIZONTAL = "horizontal";
private static final Rect LOGICAL_OFFSET_EMPTY = new Rect();
// When we first initialize a backing view, we create a view we are going to throw away anyway,
// so instead initialize with a shared view.
private static final DrawView EMPTY_DRAW_VIEW = new DrawView(0);
private DrawCommand[] mDrawCommands = DrawCommand.EMPTY_ARRAY;
private AttachDetachListener[] mAttachDetachListeners = AttachDetachListener.EMPTY_ARRAY;
private NodeRegion[] mNodeRegions = NodeRegion.EMPTY_ARRAY;
private FlatShadowNode[] mNativeChildren = FlatShadowNode.EMPTY_ARRAY;
private NodeRegion mNodeRegion = NodeRegion.EMPTY;
private int mNativeParentTag;
private int mViewLeft;
private int mViewTop;
private int mViewRight;
private int mViewBottom;
private boolean mBackingViewIsCreated;
private @Nullable DrawView mDrawView;
private @Nullable DrawBackgroundColor mDrawBackground;
private boolean mIsUpdated = true;
private boolean mForceMountChildrenToView;
private float mClipLeft;
private float mClipTop;
private float mClipRight;
private float mClipBottom;
// Used to track whether any of the NodeRegions overflow this Node. This is used to determine
// whether or not we can detach this Node in the context of a container with
// setRemoveClippedSubviews enabled.
private boolean mOverflowsContainer;
// this Rect contains the offset to get the "logical bounds" (i.e. bounds that include taking
// into account overflow visible).
private Rect mLogicalOffset = LOGICAL_OFFSET_EMPTY;
// last OnLayoutEvent info, only used when shouldNotifyOnLayout() is true.
private int mLayoutX;
private int mLayoutY;
private int mLayoutWidth;
private int mLayoutHeight;
// clip radius
float mClipRadius;
boolean mClipToBounds = false;
/* package */ void handleUpdateProperties(ReactStylesDiffMap styles) {
if (!mountsToView()) {
// Make sure we mount this FlatShadowNode to a View if any of these properties are present.
if (styles.hasKey(PROP_OPACITY) ||
styles.hasKey(PROP_RENDER_TO_HARDWARE_TEXTURE) ||
styles.hasKey(PROP_TEST_ID) ||
styles.hasKey(PROP_ACCESSIBILITY_LABEL) ||
styles.hasKey(PROP_ACCESSIBILITY_COMPONENT_TYPE) ||
styles.hasKey(PROP_ACCESSIBILITY_LIVE_REGION) ||
styles.hasKey(PROP_TRANSFORM) ||
styles.hasKey(PROP_IMPORTANT_FOR_ACCESSIBILITY) ||
styles.hasKey(PROP_REMOVE_CLIPPED_SUBVIEWS)) {
forceMountToView();
}
}
}
/* package */ final void forceMountChildrenToView() {
if (mForceMountChildrenToView) {
return;
}
mForceMountChildrenToView = true;
for (int i = 0, childCount = getChildCount(); i != childCount; ++i) {
ReactShadowNode child = getChildAt(i);
if (child instanceof FlatShadowNode) {
((FlatShadowNode) child).forceMountToView();
}
}
}
/**
* Collects DrawCommands produced by this FlatShadowNode.
*/
protected void collectState(
StateBuilder stateBuilder,
float left,
float top,
float right,
float bottom,
float clipLeft,
float clipTop,
float clipRight,
float clipBottom) {
if (mDrawBackground != null) {
mDrawBackground = (DrawBackgroundColor) mDrawBackground.updateBoundsAndFreeze(
left,
top,
right,
bottom,
clipLeft,
clipTop,
clipRight,
clipBottom);
stateBuilder.addDrawCommand(mDrawBackground);
}
}
/**
* Return whether or not this node draws anything
*
* This is used to decide whether or not to collect the NodeRegion for this node. This ensures
* that any FlatShadowNode that does not emit any DrawCommands should not bother handling touch
* (i.e. if it draws absolutely nothing, it is, for all intents and purposes, a layout only node).
*
* @return whether or not this is node draws anything
*/
boolean doesDraw() {
// if it mounts to view or draws a background, we can collect it - otherwise, no, unless a
// child suggests some alternative behavior
return mDrawView != null || mDrawBackground != null;
}
@ReactProp(name = ViewProps.BACKGROUND_COLOR)
public void setBackgroundColor(int backgroundColor) {
mDrawBackground = (backgroundColor == 0) ? null : new DrawBackgroundColor(backgroundColor);
invalidate();
}
@Override
public void setOverflow(String overflow) {
super.setOverflow(overflow);
mClipToBounds = "hidden".equals(overflow);
if (mClipToBounds) {
mOverflowsContainer = false;
if (mClipRadius > DrawView.MINIMUM_ROUNDED_CLIPPING_VALUE) {
// mount to a view if we are overflow: hidden and are clipping, so that we can do one
// clipPath to clip all the children of this node (both DrawCommands and Views).
forceMountToView();
}
} else {
updateOverflowsContainer();
}
invalidate();
}
public final boolean clipToBounds() {
return mClipToBounds;
}
@Override
public final int getScreenX() {
return mViewLeft;
}
@Override
public final int getScreenY() {
return mViewTop;
}
@Override
public final int getScreenWidth() {
if (mountsToView()) {
return mViewRight - mViewLeft;
} else {
return Math.round(mNodeRegion.getRight() - mNodeRegion.getLeft());
}
}
@Override
public final int getScreenHeight() {
if (mountsToView()) {
return mViewBottom - mViewTop;
} else {
return Math.round(mNodeRegion.getBottom() - mNodeRegion.getTop());
}
}
@Override
public void addChildAt(ReactShadowNodeImpl child, int i) {
super.addChildAt(child, i);
if (mForceMountChildrenToView && child instanceof FlatShadowNode) {
((FlatShadowNode) child).forceMountToView();
}
}
/**
* Marks root node as updated to trigger a StateBuilder pass to collect DrawCommands for the node
* tree. Use it when FlatShadowNode is updated but doesn't require a layout pass (e.g. background
* color is changed).
*/
protected final void invalidate() {
FlatShadowNode node = this;
while (true) {
if (node.mountsToView()) {
if (node.mIsUpdated) {
// already updated
return;
}
node.mIsUpdated = true;
}
ReactShadowNode parent = node.getParent();
if (parent == null) {
// not attached to a hierarchy yet
return;
}
node = (FlatShadowNode) parent;
}
}
@Override
public void markUpdated() {
super.markUpdated();
mIsUpdated = true;
invalidate();
}
/* package */ final boolean isUpdated() {
return mIsUpdated;
}
/* package */ final void resetUpdated() {
mIsUpdated = false;
}
/* package */ final boolean clipBoundsChanged(
float clipLeft,
float clipTop,
float clipRight,
float clipBottom) {
return mClipLeft != clipLeft || mClipTop != clipTop ||
mClipRight != clipRight || mClipBottom != clipBottom;
}
/* package */ final void setClipBounds(
float clipLeft,
float clipTop,
float clipRight,
float clipBottom) {
mClipLeft = clipLeft;
mClipTop = clipTop;
mClipRight = clipRight;
mClipBottom = clipBottom;
}
/**
* Returns an array of DrawCommands to perform during the View's draw pass.
*/
/* package */ final 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 needs to be redrawn.
*/
/* package */ final void setDrawCommands(DrawCommand[] drawCommands) {
mDrawCommands = drawCommands;
}
/**
* Sets an array of AttachDetachListeners to call onAttach/onDetach when they are attached to or
* detached from a View that this shadow node maps to.
*/
/* package */ final void setAttachDetachListeners(AttachDetachListener[] listeners) {
mAttachDetachListeners = listeners;
}
/**
* Returns an array of AttachDetachListeners associated with this shadow node.
*/
/* package */ final AttachDetachListener[] getAttachDetachListeners() {
return mAttachDetachListeners;
}
/* package */ final FlatShadowNode[] getNativeChildren() {
return mNativeChildren;
}
/* package */ final void setNativeChildren(FlatShadowNode[] nativeChildren) {
mNativeChildren = nativeChildren;
}
/* package */ final int getNativeParentTag() {
return mNativeParentTag;
}
/* package */ final void setNativeParentTag(int nativeParentTag) {
mNativeParentTag = nativeParentTag;
}
/* package */ final NodeRegion[] getNodeRegions() {
return mNodeRegions;
}
/* package */ final void setNodeRegions(NodeRegion[] nodeRegion) {
mNodeRegions = nodeRegion;
updateOverflowsContainer();
}
/* package */ final void updateOverflowsContainer() {
boolean overflowsContainer = false;
int width = (int) (mNodeRegion.getRight() - mNodeRegion.getLeft());
int height = (int) (mNodeRegion.getBottom() - mNodeRegion.getTop());
float leftBound = 0;
float rightBound = width;
float topBound = 0;
float bottomBound = height;
Rect logicalOffset = null;
// when we are overflow:visible, we try to figure out if any of the children are outside
// of the bounds of this view. since NodeRegion bounds are relative to their parent (i.e.
// 0, 0 is always the start), we see how much outside of the bounds we are (negative left
// or top, or bottom that's more than height or right that's more than width). we set these
// offsets in mLogicalOffset for being able to more intelligently determine whether or not
// to clip certain subviews.
if (!mClipToBounds && height > 0 && width > 0) {
for (NodeRegion region : mNodeRegions) {
if (region.getLeft() < leftBound) {
leftBound = region.getLeft();
overflowsContainer = true;
}
if (region.getRight() > rightBound) {
rightBound = region.getRight();
overflowsContainer = true;
}
if (region.getTop() < topBound) {
topBound = region.getTop();
overflowsContainer = true;
}
if (region.getBottom() > bottomBound) {
bottomBound = region.getBottom();
overflowsContainer = true;
}
}
if (overflowsContainer) {
logicalOffset = new Rect(
(int) leftBound,
(int) topBound,
(int) (rightBound - width),
(int) (bottomBound - height));
}
}
// if we don't overflow, let's check if any of the immediate children overflow.
// this is "indirectly recursive," since this method is called when setNodeRegions is called,
// and the children call setNodeRegions before their parent. consequently, when a node deep
// inside the tree overflows, its immediate parent has mOverflowsContainer set to true, and,
// by extension, so do all of its ancestors, sufficing here to only check the immediate
// child's mOverflowsContainer value instead of recursively asking if each child overflows its
// container.
if (!overflowsContainer && mNodeRegion != NodeRegion.EMPTY) {
int children = getChildCount();
for (int i = 0; i < children; i++) {
ReactShadowNode node = getChildAt(i);
if (node instanceof FlatShadowNode && ((FlatShadowNode) node).mOverflowsContainer) {
Rect childLogicalOffset = ((FlatShadowNode) node).mLogicalOffset;
if (logicalOffset == null) {
logicalOffset = new Rect();
}
// TODO: t11674025 - improve this - a grandparent may end up having smaller logical
// bounds than its children (because the grandparent's size may be larger than that of
// its child, so the grandchild overflows its parent but not its grandparent). currently,
// if a 100x100 view has a 5x5 view, and inside it has a 10x10 view, the inner most view
// overflows its parent but not its grandparent - the logical bounds on the grandparent
// will still be 5x5 (because they're inherited from the child's logical bounds). this
// has the effect of causing us to clip 5px later than we really have to.
logicalOffset.union(childLogicalOffset);
overflowsContainer = true;
}
}
}
// if things changed, notify the parent(s) about said changes - while in many cases, this will
// be extra work (since we process this for the parents after the children), in some cases,
// we may have no new node regions in the parent, but have a new node region in the child, and,
// as a result, the parent may not get the correct value for overflows container.
if (mOverflowsContainer != overflowsContainer) {
mOverflowsContainer = overflowsContainer;
mLogicalOffset = logicalOffset == null ? LOGICAL_OFFSET_EMPTY : logicalOffset;
}
}
/* package */ void updateNodeRegion(
float left,
float top,
float right,
float bottom,
boolean isVirtual) {
if (!mNodeRegion.matches(left, top, right, bottom, isVirtual)) {
setNodeRegion(new NodeRegion(left, top, right, bottom, getReactTag(), isVirtual));
}
}
protected final void setNodeRegion(NodeRegion nodeRegion) {
mNodeRegion = nodeRegion;
updateOverflowsContainer();
}
/* package */ final NodeRegion getNodeRegion() {
return mNodeRegion;
}
/**
* Sets boundaries of the View that this node maps to relative to the parent left/top coordinate.
*/
/* package */ final 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 */ final int getViewLeft() {
return mViewLeft;
}
/**
* Top position of the View this node maps to relative to the parent View.
*/
/* package */ final int getViewTop() {
return mViewTop;
}
/**
* Right position of the View this node maps to relative to the parent View.
*/
/* package */ final int getViewRight() {
return mViewRight;
}
/**
* Bottom position of the View this node maps to relative to the parent View.
*/
/* package */ final int getViewBottom() {
return mViewBottom;
}
/* package */ final void forceMountToView() {
if (isVirtual()) {
return;
}
if (mDrawView == null) {
// Create a new DrawView, but we might not know our react tag yet, so set it to 0 in the
// meantime.
mDrawView = EMPTY_DRAW_VIEW;
invalidate();
// reset NodeRegion to allow it getting garbage-collected
mNodeRegion = NodeRegion.EMPTY;
}
}
/* package */ final DrawView collectDrawView(
float left,
float top,
float right,
float bottom,
float clipLeft,
float clipTop,
float clipRight,
float clipBottom) {
Assertions.assumeNotNull(mDrawView);
if (mDrawView == EMPTY_DRAW_VIEW) {
// This is the first time we have collected this DrawView, but we have to create a new
// DrawView anyway, as reactTag is final, and our DrawView instance is the static copy.
mDrawView = new DrawView(getReactTag());
}
// avoid path clipping if overflow: visible
float clipRadius = mClipToBounds ? mClipRadius : 0.0f;
// We have the correct react tag, but we may need a new copy with updated bounds. If the bounds
// match or were never set, the same view is returned.
mDrawView = mDrawView.collectDrawView(
left,
top,
right,
bottom,
left + mLogicalOffset.left,
top + mLogicalOffset.top,
right + mLogicalOffset.right,
bottom + mLogicalOffset.bottom,
clipLeft,
clipTop,
clipRight,
clipBottom,
clipRadius);
return mDrawView;
}
@Nullable
/* package */ final OnLayoutEvent obtainLayoutEvent(int x, int y, int width, int height) {
if (mLayoutX == x && mLayoutY == y && mLayoutWidth == width && mLayoutHeight == height) {
return null;
}
mLayoutX = x;
mLayoutY = y;
mLayoutWidth = width;
mLayoutHeight = height;
return OnLayoutEvent.obtain(getReactTag(), x, y, width, height);
}
/* package */ final boolean mountsToView() {
return mDrawView != null;
}
/* package */ final boolean isBackingViewCreated() {
return mBackingViewIsCreated;
}
/* package */ final void signalBackingViewIsCreated() {
mBackingViewIsCreated = true;
}
public boolean clipsSubviews() {
return false;
}
public boolean isHorizontal() {
return false;
}
}

View File

@ -1,81 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
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 {
// these 2 are only used between collectText() and applySpans() calls.
private int mTextBegin;
private int mTextEnd;
/**
* 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;
}
/**
* Recursively visits FlatTextShadowNode and its children,
* appending text to SpannableStringBuilder.
*/
/* package */ final void collectText(SpannableStringBuilder builder) {
mTextBegin = builder.length();
performCollectText(builder);
mTextEnd = builder.length();
}
/**
* Whether or not to allow empty spans to be set
* This is used to bypass an optimization in {@code applySpans} that skips applying spans if
* there is no text (since, for TextInput, for example, we want to apply the span even if there
* is no text so that newly typed text gets styled properly).
*
* @return a boolean representing whether or not we should allow empty spans
*/
/* package */ boolean shouldAllowEmptySpans() {
return false;
}
/* package */ boolean isEditable() {
return false;
}
/**
* Recursively visits FlatTextShadowNode and its children,
* applying spans to SpannableStringBuilder.
*/
/* package */ final void applySpans(SpannableStringBuilder builder, boolean isEditable) {
if (mTextBegin != mTextEnd || shouldAllowEmptySpans()) {
performApplySpans(builder, mTextBegin, mTextEnd, isEditable);
}
}
protected abstract void performCollectText(SpannableStringBuilder builder);
protected abstract void performApplySpans(
SpannableStringBuilder builder,
int begin,
int end,
boolean isEditable);
protected abstract void performCollectAttachDetachListeners(StateBuilder stateBuilder);
}

View File

@ -1,608 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.modules.fresco.FrescoModule;
import com.facebook.react.modules.i18nmanager.I18nUtil;
import com.facebook.react.uimanager.ReactShadowNode;
import com.facebook.react.uimanager.ReactStylesDiffMap;
import com.facebook.react.uimanager.UIImplementation;
import com.facebook.react.uimanager.ViewManager;
import com.facebook.react.uimanager.ViewManagerRegistry;
import com.facebook.react.uimanager.events.EventDispatcher;
import com.facebook.yoga.YogaDirection;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
/**
* FlatUIImplementation builds on top of UIImplementation and allows pre-creating everything
* required for drawing (DrawCommands) and touching (NodeRegions) views in background thread
* for faster drawing and interactions.
*/
public class FlatUIImplementation extends UIImplementation {
private static final Map<String, Class<? extends ViewManager>> flatManagerClassMap;
static {
flatManagerClassMap = new HashMap<>();
flatManagerClassMap.put(RCTViewManager.REACT_CLASS, RCTViewManager.class);
flatManagerClassMap.put(RCTTextManager.REACT_CLASS, RCTTextManager.class);
flatManagerClassMap.put(RCTRawTextManager.REACT_CLASS, RCTRawTextManager.class);
flatManagerClassMap.put(RCTVirtualTextManager.REACT_CLASS, RCTVirtualTextManager.class);
flatManagerClassMap.put(RCTTextInlineImageManager.REACT_CLASS, RCTTextInlineImageManager.class);
flatManagerClassMap.put(RCTImageViewManager.REACT_CLASS, RCTImageViewManager.class);
flatManagerClassMap.put(RCTTextInputManager.REACT_CLASS, RCTTextInputManager.class);
flatManagerClassMap.put(RCTViewPagerManager.REACT_CLASS, RCTViewPagerManager.class);
flatManagerClassMap.put(FlatARTSurfaceViewManager.REACT_CLASS, FlatARTSurfaceViewManager.class);
flatManagerClassMap.put(RCTModalHostManager.REACT_CLASS, RCTModalHostManager.class);
}
/**
* Build the map of view managers, checking that the managers FlatUI requires are correctly
* overridden.
*/
private static Map<String, ViewManager> buildViewManagerMap(List<ViewManager> viewManagers) {
Map<String, ViewManager> viewManagerMap = new HashMap<>();
for (ViewManager viewManager : viewManagers) {
viewManagerMap.put(viewManager.getName(), viewManager);
}
for (Map.Entry<String, Class<? extends ViewManager>> entry : flatManagerClassMap.entrySet()) {
String name = entry.getKey();
ViewManager maybeFlatViewManager = viewManagerMap.get(name);
if (maybeFlatViewManager == null) {
// We don't have a view manager for this name in the package, no need to add one.
continue;
}
Class<? extends ViewManager> flatClazz = entry.getValue();
if (maybeFlatViewManager.getClass() != flatClazz) {
// If we have instances that have flat equivalents, override them.
try {
viewManagerMap.put(name, flatClazz.newInstance());
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to access flat class for " + name, e);
} catch (InstantiationException e) {
throw new RuntimeException("Unable to instantiate flat class for " + name, e);
}
}
}
return viewManagerMap;
}
public static FlatUIImplementation createInstance(
ReactApplicationContext reactContext,
List<ViewManager> viewManagers,
EventDispatcher eventDispatcher,
boolean memoryImprovementEnabled,
int minTimeLeftInFrameForNonBatchedOperationMs) {
Map<String, ViewManager> viewManagerMap = buildViewManagerMap(viewManagers);
RCTImageViewManager imageViewManager =
(RCTImageViewManager) viewManagerMap.get(RCTImageViewManager.REACT_CLASS);
if (imageViewManager != null) {
Object callerContext = imageViewManager.getCallerContext();
if (callerContext != null) {
RCTImageView.setCallerContext(callerContext);
}
}
DraweeRequestHelper.setResources(reactContext.getResources());
TypefaceCache.setAssetManager(reactContext.getAssets());
ViewManagerRegistry viewManagerRegistry = new ViewManagerRegistry(viewManagerMap);
FlatNativeViewHierarchyManager nativeViewHierarchyManager = new FlatNativeViewHierarchyManager(
viewManagerRegistry);
FlatUIViewOperationQueue operationsQueue =
new FlatUIViewOperationQueue(
reactContext, nativeViewHierarchyManager, minTimeLeftInFrameForNonBatchedOperationMs);
return new FlatUIImplementation(
reactContext,
imageViewManager,
viewManagerRegistry,
operationsQueue,
eventDispatcher,
memoryImprovementEnabled
);
}
/**
* Helper class that sorts moveTo/moveFrom arrays passed to #manageChildren().
* Not used outside of the said method.
*/
private final MoveProxy mMoveProxy = new MoveProxy();
private final ReactApplicationContext mReactContext;
private @Nullable RCTImageViewManager mRCTImageViewManager;
private final StateBuilder mStateBuilder;
private final boolean mMemoryImprovementEnabled;
private FlatUIImplementation(
ReactApplicationContext reactContext,
@Nullable RCTImageViewManager rctImageViewManager,
ViewManagerRegistry viewManagers,
FlatUIViewOperationQueue operationsQueue,
EventDispatcher eventDispatcher,
boolean memoryImprovementEnabled) {
super(reactContext, viewManagers, operationsQueue, eventDispatcher);
mReactContext = reactContext;
mRCTImageViewManager = rctImageViewManager;
mStateBuilder = new StateBuilder(operationsQueue);
mMemoryImprovementEnabled = memoryImprovementEnabled;
}
@Override
protected ReactShadowNode createRootShadowNode() {
if (mRCTImageViewManager != null) {
// This is not the best place to initialize DraweeRequestHelper, but order of module
// initialization is undefined, and this is pretty much the earliest when we are guarantied
// that Fresco is initialized and DraweeControllerBuilder can be queried. This also happens
// relatively rarely to have any performance considerations.
mReactContext.getNativeModule(FrescoModule.class); // initialize Fresco
DraweeRequestHelper.setDraweeControllerBuilder(
mRCTImageViewManager.getDraweeControllerBuilder());
mRCTImageViewManager = null;
}
ReactShadowNode node = new FlatRootShadowNode();
I18nUtil sharedI18nUtilInstance = I18nUtil.getInstance();
if (sharedI18nUtilInstance.isRTL(mReactContext)) {
node.setLayoutDirection(YogaDirection.RTL);
}
return node;
}
@Override
protected ReactShadowNode createShadowNode(String className) {
ReactShadowNode cssNode = super.createShadowNode(className);
if (cssNode instanceof FlatShadowNode || cssNode.isVirtual()) {
return cssNode;
}
ViewManager viewManager = resolveViewManager(className);
return new NativeViewWrapper(viewManager);
}
@Override
protected void handleCreateView(
ReactShadowNode cssNode,
int rootViewTag,
@Nullable ReactStylesDiffMap styles) {
if (cssNode instanceof FlatShadowNode) {
FlatShadowNode node = (FlatShadowNode) cssNode;
if (styles != null) {
node.handleUpdateProperties(styles);
}
if (node.mountsToView()) {
mStateBuilder.enqueueCreateOrUpdateView(node, styles);
}
} else {
super.handleCreateView(cssNode, rootViewTag, styles);
}
}
@Override
protected void handleUpdateView(
ReactShadowNode cssNode,
String className,
ReactStylesDiffMap styles) {
if (cssNode instanceof FlatShadowNode) {
FlatShadowNode node = (FlatShadowNode) cssNode;
node.handleUpdateProperties(styles);
if (node.mountsToView()) {
mStateBuilder.enqueueCreateOrUpdateView(node, styles);
}
} else {
super.handleUpdateView(cssNode, className, styles);
}
}
@Override
public void manageChildren(
int viewTag,
@Nullable ReadableArray moveFrom,
@Nullable ReadableArray moveTo,
@Nullable ReadableArray addChildTags,
@Nullable ReadableArray addAtIndices,
@Nullable ReadableArray removeFrom) {
ReactShadowNode parentNode = resolveShadowNode(viewTag);
// moveFrom and removeFrom are defined in original order before any mutations.
removeChildren(parentNode, moveFrom, moveTo, removeFrom);
// moveTo and addAtIndices are defined in final order after all the mutations applied.
addChildren(parentNode, addChildTags, addAtIndices);
}
@Override
public void setChildren(
int viewTag,
ReadableArray children) {
ReactShadowNode parentNode = resolveShadowNode(viewTag);
for (int i = 0; i < children.size(); i++) {
ReactShadowNode addToChild = resolveShadowNode(children.getInt(i));
addChildAt(parentNode, addToChild, i, i - 1);
}
}
@Override
public void measure(int reactTag, Callback callback) {
measureHelper(reactTag, false, callback);
}
private void measureHelper(int reactTag, boolean relativeToWindow, Callback callback) {
FlatShadowNode node = (FlatShadowNode) resolveShadowNode(reactTag);
if (node.mountsToView()) {
mStateBuilder.ensureBackingViewIsCreated(node);
if (relativeToWindow) {
super.measureInWindow(reactTag, callback);
} else {
super.measure(reactTag, callback);
}
return;
}
// virtual nodes do not have values for width and height, so get these values
// from the first non-virtual parent node
while (node != null && node.isVirtual()) {
node = (FlatShadowNode) node.getParent();
}
if (node == null) {
// everything is virtual, this shouldn't happen so just silently return
return;
}
float width = node.getLayoutWidth();
float height = node.getLayoutHeight();
boolean nodeMountsToView = node.mountsToView();
// this is to avoid double-counting xInParent and yInParent when we visit
// the while loop, below.
float xInParent = nodeMountsToView ? node.getLayoutX() : 0;
float yInParent = nodeMountsToView ? node.getLayoutY() : 0;
while (!node.mountsToView()) {
if (!node.isVirtual()) {
xInParent += node.getLayoutX();
yInParent += node.getLayoutY();
}
node = Assertions.assumeNotNull((FlatShadowNode) node.getParent());
}
float parentWidth = node.getLayoutWidth();
float parentHeight = node.getLayoutHeight();
FlatUIViewOperationQueue operationsQueue = mStateBuilder.getOperationsQueue();
operationsQueue.enqueueMeasureVirtualView(
node.getReactTag(),
xInParent / parentWidth,
yInParent / parentHeight,
width / parentWidth,
height / parentHeight,
relativeToWindow,
callback);
}
private void ensureMountsToViewAndBackingViewIsCreated(int reactTag) {
FlatShadowNode node = (FlatShadowNode) resolveShadowNode(reactTag);
if (node.isBackingViewCreated()) {
return;
}
node.forceMountToView();
mStateBuilder.ensureBackingViewIsCreated(node);
}
@Override
public void findSubviewIn(int reactTag, float targetX, float targetY, Callback callback) {
ensureMountsToViewAndBackingViewIsCreated(reactTag);
super.findSubviewIn(reactTag, targetX, targetY, callback);
}
@Override
public void measureInWindow(int reactTag, Callback callback) {
measureHelper(reactTag, true, callback);
}
@Override
public void addAnimation(int reactTag, int animationID, Callback onSuccess) {
ensureMountsToViewAndBackingViewIsCreated(reactTag);
super.addAnimation(reactTag, animationID, onSuccess);
}
@Override
public void dispatchViewManagerCommand(int reactTag, int commandId, ReadableArray commandArgs) {
// Make sure that our target view is actually a view, then delay command dispatch until after
// we have updated the view hierarchy.
ensureMountsToViewAndBackingViewIsCreated(reactTag);
mStateBuilder.enqueueViewManagerCommand(reactTag, commandId, commandArgs);
}
@Override
public void showPopupMenu(int reactTag, ReadableArray items, Callback error, Callback success) {
ensureMountsToViewAndBackingViewIsCreated(reactTag);
super.showPopupMenu(reactTag, items, error, success);
}
@Override
public void sendAccessibilityEvent(int reactTag, int eventType) {
ensureMountsToViewAndBackingViewIsCreated(reactTag);
super.sendAccessibilityEvent(reactTag, eventType);
}
/**
* Removes all children defined by moveFrom and removeFrom from a given parent,
* preparing elements in moveFrom to be re-added at proper index.
*/
private void removeChildren(
ReactShadowNode parentNode,
@Nullable ReadableArray moveFrom,
@Nullable ReadableArray moveTo,
@Nullable ReadableArray removeFrom) {
int prevIndex = Integer.MAX_VALUE;
mMoveProxy.setup(moveFrom, moveTo);
int moveFromIndex = mMoveProxy.size() - 1;
int moveFromChildIndex = (moveFromIndex == -1) ? -1 : mMoveProxy.getMoveFrom(moveFromIndex);
int numToRemove = removeFrom == null ? 0 : removeFrom.size();
int[] indicesToRemove = new int[numToRemove];
if (numToRemove > 0) {
Assertions.assertNotNull(removeFrom);
for (int i = 0; i < numToRemove; i++) {
int indexToRemove = removeFrom.getInt(i);
indicesToRemove[i] = indexToRemove;
}
}
// this isn't guaranteed to be sorted actually
Arrays.sort(indicesToRemove);
int removeFromIndex;
int removeFromChildIndex;
if (removeFrom == null) {
removeFromIndex = -1;
removeFromChildIndex = -1;
} else {
removeFromIndex = indicesToRemove.length - 1;
removeFromChildIndex = indicesToRemove[removeFromIndex];
}
// both moveFrom and removeFrom are already sorted, but combined order is not sorted. Use
// a merge step from mergesort to walk over both arrays and extract elements in sorted order.
while (true) {
if (moveFromChildIndex > removeFromChildIndex) {
moveChild(removeChildAt(parentNode, moveFromChildIndex, prevIndex), moveFromIndex);
prevIndex = moveFromChildIndex;
--moveFromIndex;
moveFromChildIndex = (moveFromIndex == -1) ? -1 : mMoveProxy.getMoveFrom(moveFromIndex);
} else if (removeFromChildIndex > moveFromChildIndex) {
removeChild(removeChildAt(parentNode, removeFromChildIndex, prevIndex), parentNode);
prevIndex = removeFromChildIndex;
--removeFromIndex;
removeFromChildIndex = (removeFromIndex == -1) ? -1 : indicesToRemove[removeFromIndex];
} else {
// moveFromChildIndex == removeFromChildIndex can only be if both are equal to -1
// which means that we exhausted both arrays, and all children are removed.
break;
}
}
}
/**
* Unregisters given element and all of its children from ShadowNodeRegistry,
* and drops all Views used by it and its children.
*/
private void removeChild(ReactShadowNode child, ReactShadowNode parentNode) {
dropNativeViews(child, parentNode);
removeShadowNode(child);
}
private void dropNativeViews(ReactShadowNode child, ReactShadowNode parentNode) {
if (child instanceof FlatShadowNode) {
FlatShadowNode node = (FlatShadowNode) child;
if (node.mountsToView() && node.isBackingViewCreated()) {
int tag = -1;
// this tag is used to remove the reference to this dropping view if it it's clipped.
// we need to figure out the correct "view parent" tag to do this. note that this is
// not necessarily getParent().getReactTag(), since getParent() may represent something
// that's not a View - we need to find the first View (what would represent
// view.getParent() on the ui thread), which is what this code is finding.
ReactShadowNode tmpNode = parentNode;
while (tmpNode != null) {
if (tmpNode instanceof FlatShadowNode) {
FlatShadowNode flatTmpNode = (FlatShadowNode) tmpNode;
if (flatTmpNode.mountsToView() && flatTmpNode.isBackingViewCreated() &&
flatTmpNode.getParent() != null) {
tag = flatTmpNode.getReactTag();
break;
}
}
tmpNode = tmpNode.getParent();
}
// this will recursively drop all subviews
mStateBuilder.dropView(node, tag);
return;
}
}
for (int i = 0, childCount = child.getChildCount(); i != childCount; ++i) {
dropNativeViews(child.getChildAt(i), child);
}
}
/**
* Prepares a given element to be moved to a new position.
*/
private void moveChild(ReactShadowNode child, int moveFromIndex) {
mMoveProxy.setChildMoveFrom(moveFromIndex, child);
}
/**
* Adds all children from addChildTags and moveFrom/moveTo.
*/
private void addChildren(
ReactShadowNode parentNode,
@Nullable ReadableArray addChildTags,
@Nullable ReadableArray addAtIndices) {
int prevIndex = -1;
int moveToIndex;
int moveToChildIndex;
if (mMoveProxy.size() == 0) {
moveToIndex = Integer.MAX_VALUE;
moveToChildIndex = Integer.MAX_VALUE;
} else {
moveToIndex = 0;
moveToChildIndex = mMoveProxy.getMoveTo(0);
}
int numNodesToAdd;
int addToIndex;
int addToChildIndex;
if (addAtIndices == null) {
numNodesToAdd = 0;
addToIndex = Integer.MAX_VALUE;
addToChildIndex = Integer.MAX_VALUE;
} else {
numNodesToAdd = addAtIndices.size();
addToIndex = 0;
addToChildIndex = addAtIndices.getInt(0);
}
// both mMoveProxy and addChildTags are already sorted, but combined order is not sorted. Use
// a merge step from mergesort to walk over both arrays and extract elements in sorted order.
while (true) {
if (addToChildIndex < moveToChildIndex) {
ReactShadowNode addToChild = resolveShadowNode(addChildTags.getInt(addToIndex));
addChildAt(parentNode, addToChild, addToChildIndex, prevIndex);
prevIndex = addToChildIndex;
++addToIndex;
if (addToIndex == numNodesToAdd) {
addToChildIndex = Integer.MAX_VALUE;
} else {
addToChildIndex = addAtIndices.getInt(addToIndex);
}
} else if (moveToChildIndex < addToChildIndex) {
ReactShadowNode moveToChild = mMoveProxy.getChildMoveTo(moveToIndex);
addChildAt(parentNode, moveToChild, moveToChildIndex, prevIndex);
prevIndex = moveToChildIndex;
++moveToIndex;
if (moveToIndex == mMoveProxy.size()) {
moveToChildIndex = Integer.MAX_VALUE;
} else {
moveToChildIndex = mMoveProxy.getMoveTo(moveToIndex);
}
} else {
// moveToChildIndex == addToChildIndex can only be if both are equal to Integer.MAX_VALUE
// which means that we exhausted both arrays, and all children are added.
break;
}
}
}
/**
* Removes a child from parent, verifying that we are removing in descending order.
*/
private static ReactShadowNode removeChildAt(
ReactShadowNode parentNode,
int index,
int prevIndex) {
if (index >= prevIndex) {
throw new RuntimeException(
"Invariant failure, needs sorting! " + index + " >= " + prevIndex);
}
return parentNode.removeChildAt(index);
}
/**
* Adds a child to parent, verifying that we are adding in ascending order.
*/
private static void addChildAt(
ReactShadowNode parentNode,
ReactShadowNode childNode,
int index,
int prevIndex) {
if (index <= prevIndex) {
throw new RuntimeException(
"Invariant failure, needs sorting! " + index + " <= " + prevIndex);
}
parentNode.addChildAt(childNode, index);
}
@Override
protected void updateViewHierarchy() {
super.updateViewHierarchy();
mStateBuilder.afterUpdateViewHierarchy(mEventDispatcher);
}
@Override
protected void applyUpdatesRecursive(
ReactShadowNode cssNode,
float absoluteX,
float absoluteY) {
mStateBuilder.applyUpdates((FlatRootShadowNode) cssNode);
}
@Override
public void removeRootView(int rootViewTag) {
if (mMemoryImprovementEnabled) {
removeRootShadowNode(rootViewTag);
}
mStateBuilder.removeRootView(rootViewTag);
}
@Override
public void setJSResponder(int possiblyVirtualReactTag, boolean blockNativeResponder) {
ReactShadowNode node = resolveShadowNode(possiblyVirtualReactTag);
while (node.isVirtual()) {
node = node.getParent();
}
int tag = node.getReactTag();
// if the node in question doesn't mount to a View, find the first parent that does mount to
// a View. without this, we'll crash when we try to set the JSResponder, since part of that
// is to find the parent view and ask it to not intercept touch events.
while (node instanceof FlatShadowNode && !((FlatShadowNode) node).mountsToView()) {
node = node.getParent();
}
FlatUIViewOperationQueue operationsQueue = mStateBuilder.getOperationsQueue();
operationsQueue.enqueueSetJSResponder(
node == null ? tag : node.getReactTag(),
possiblyVirtualReactTag,
blockNativeResponder);
}
}

View File

@ -1,527 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import com.facebook.react.uimanager.UIViewOperationQueue;
import android.util.SparseIntArray;
import android.view.View;
import android.view.ViewGroup;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.uimanager.IllegalViewOperationException;
import com.facebook.react.uimanager.NoSuchNativeViewException;
import com.facebook.react.uimanager.PixelUtil;
import com.facebook.react.uimanager.TouchTargetHelper;
import com.facebook.react.uimanager.UIViewOperationQueue;
import java.util.ArrayList;
import javax.annotation.Nullable;
/**
* FlatUIViewOperationQueue extends {@link UIViewOperationQueue} to add
* FlatUIImplementation-specific methods that need to run in UI thread.
*/
/* package */ final class FlatUIViewOperationQueue extends UIViewOperationQueue {
private static final int[] MEASURE_BUFFER = new int[4];
private final FlatNativeViewHierarchyManager mNativeViewHierarchyManager;
private final ProcessLayoutRequests mProcessLayoutRequests = new ProcessLayoutRequests();
private final class ProcessLayoutRequests implements UIViewOperationQueue.UIOperation {
@Override
public void execute() {
FlatViewGroup.processLayoutRequests();
}
}
/**
* UIOperation that updates DrawCommands for a View defined by reactTag.
*/
private final class UpdateMountState implements UIViewOperationQueue.UIOperation {
private final int mReactTag;
private final @Nullable DrawCommand[] mDrawCommands;
private final @Nullable AttachDetachListener[] mAttachDetachListeners;
private final @Nullable NodeRegion[] mNodeRegions;
private UpdateMountState(
int reactTag,
@Nullable DrawCommand[] drawCommands,
@Nullable AttachDetachListener[] listeners,
@Nullable NodeRegion[] nodeRegions) {
mReactTag = reactTag;
mDrawCommands = drawCommands;
mAttachDetachListeners = listeners;
mNodeRegions = nodeRegions;
}
@Override
public void execute() {
mNativeViewHierarchyManager.updateMountState(
mReactTag,
mDrawCommands,
mAttachDetachListeners,
mNodeRegions);
}
}
/**
* UIOperation that updates DrawCommands for a View defined by reactTag.
*/
private final class UpdateClippingMountState implements UIViewOperationQueue.UIOperation {
private final int mReactTag;
private final @Nullable DrawCommand[] mDrawCommands;
private final SparseIntArray mDrawViewIndexMap;
private final float[] mCommandMaxBot;
private final float[] mCommandMinTop;
private final @Nullable AttachDetachListener[] mAttachDetachListeners;
private final @Nullable NodeRegion[] mNodeRegions;
private final float[] mRegionMaxBot;
private final float[] mRegionMinTop;
private final boolean mWillMountViews;
private UpdateClippingMountState(
int reactTag,
@Nullable DrawCommand[] drawCommands,
SparseIntArray drawViewIndexMap,
float[] commandMaxBot,
float[] commandMinTop,
@Nullable AttachDetachListener[] listeners,
@Nullable NodeRegion[] nodeRegions,
float[] regionMaxBot,
float[] regionMinTop,
boolean willMountViews) {
mReactTag = reactTag;
mDrawCommands = drawCommands;
mDrawViewIndexMap = drawViewIndexMap;
mCommandMaxBot = commandMaxBot;
mCommandMinTop = commandMinTop;
mAttachDetachListeners = listeners;
mNodeRegions = nodeRegions;
mRegionMaxBot = regionMaxBot;
mRegionMinTop = regionMinTop;
mWillMountViews = willMountViews;
}
@Override
public void execute() {
mNativeViewHierarchyManager.updateClippingMountState(
mReactTag,
mDrawCommands,
mDrawViewIndexMap,
mCommandMaxBot,
mCommandMinTop,
mAttachDetachListeners,
mNodeRegions,
mRegionMaxBot,
mRegionMinTop,
mWillMountViews);
}
}
private final class UpdateViewGroup implements UIViewOperationQueue.UIOperation {
private final int mReactTag;
private final int[] mViewsToAdd;
private final int[] mViewsToDetach;
private UpdateViewGroup(int reactTag, int[] viewsToAdd, int[] viewsToDetach) {
mReactTag = reactTag;
mViewsToAdd = viewsToAdd;
mViewsToDetach = viewsToDetach;
}
@Override
public void execute() {
mNativeViewHierarchyManager.updateViewGroup(mReactTag, mViewsToAdd, mViewsToDetach);
}
}
/**
* UIOperation that updates View bounds for a View defined by reactTag.
*/
public final class UpdateViewBounds implements UIViewOperationQueue.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);
}
}
private final class SetPadding implements UIViewOperationQueue.UIOperation {
private final int mReactTag;
private final int mPaddingLeft;
private final int mPaddingTop;
private final int mPaddingRight;
private final int mPaddingBottom;
private SetPadding(
int reactTag,
int paddingLeft,
int paddingTop,
int paddingRight,
int paddingBottom) {
mReactTag = reactTag;
mPaddingLeft = paddingLeft;
mPaddingTop = paddingTop;
mPaddingRight = paddingRight;
mPaddingBottom = paddingBottom;
}
@Override
public void execute() {
mNativeViewHierarchyManager.setPadding(
mReactTag,
mPaddingLeft,
mPaddingTop,
mPaddingRight,
mPaddingBottom);
}
}
private final class DropViews implements UIViewOperationQueue.UIOperation {
private final SparseIntArray mViewsToDrop;
private DropViews(ArrayList<Integer> viewsToDrop, ArrayList<Integer> parentsForViewsToDrop) {
SparseIntArray sparseIntArray = new SparseIntArray();
for (int i = 0, count = viewsToDrop.size(); i < count; i++) {
sparseIntArray.put(viewsToDrop.get(i), parentsForViewsToDrop.get(i));
}
mViewsToDrop = sparseIntArray;
}
@Override
public void execute() {
mNativeViewHierarchyManager.dropViews(mViewsToDrop);
}
}
private final class MeasureVirtualView implements UIViewOperationQueue.UIOperation {
private final int mReactTag;
private final float mScaledX;
private final float mScaledY;
private final float mScaledWidth;
private final float mScaledHeight;
private final Callback mCallback;
private final boolean mRelativeToWindow;
private MeasureVirtualView(
int reactTag,
float scaledX,
float scaledY,
float scaledWidth,
float scaledHeight,
boolean relativeToWindow,
Callback callback) {
mReactTag = reactTag;
mScaledX = scaledX;
mScaledY = scaledY;
mScaledWidth = scaledWidth;
mScaledHeight = scaledHeight;
mCallback = callback;
mRelativeToWindow = relativeToWindow;
}
@Override
public void execute() {
try {
// Measure native View
if (mRelativeToWindow) {
// relative to the window
mNativeViewHierarchyManager.measureInWindow(mReactTag, MEASURE_BUFFER);
} else {
// relative to the root view
mNativeViewHierarchyManager.measure(mReactTag, MEASURE_BUFFER);
}
} catch (NoSuchNativeViewException noSuchNativeViewException) {
// Invoke with no args to signal failure and to allow JS to clean up the callback
// handle.
mCallback.invoke();
return;
}
float nativeViewX = MEASURE_BUFFER[0];
float nativeViewY = MEASURE_BUFFER[1];
float nativeViewWidth = MEASURE_BUFFER[2];
float nativeViewHeight = MEASURE_BUFFER[3];
// Calculate size of the virtual child inside native View.
float x = PixelUtil.toDIPFromPixel(mScaledX * nativeViewWidth + nativeViewX);
float y = PixelUtil.toDIPFromPixel(mScaledY * nativeViewHeight + nativeViewY);
float width = PixelUtil.toDIPFromPixel(mScaledWidth * nativeViewWidth);
float height = PixelUtil.toDIPFromPixel(mScaledHeight * nativeViewHeight);
if (mRelativeToWindow) {
mCallback.invoke(x, y, width, height);
} else {
mCallback.invoke(0, 0, width, height, x, y);
}
}
}
public final class DetachAllChildrenFromViews implements UIViewOperationQueue.UIOperation {
private @Nullable int[] mViewsToDetachAllChildrenFrom;
public void setViewsToDetachAllChildrenFrom(int[] viewsToDetachAllChildrenFrom) {
mViewsToDetachAllChildrenFrom = viewsToDetachAllChildrenFrom;
}
@Override
public void execute() {
mNativeViewHierarchyManager.detachAllChildrenFromViews(mViewsToDetachAllChildrenFrom);
}
}
private final class FindTargetForTouchOperation implements UIViewOperationQueue.UIOperation {
private final int mReactTag;
private final float mTargetX;
private final float mTargetY;
private final Callback mCallback;
private final int[] NATIVE_VIEW_BUFFER = new int[1];
private FindTargetForTouchOperation(
final int reactTag,
final float targetX,
final float targetY,
final Callback callback) {
super();
mReactTag = reactTag;
mTargetX = targetX;
mTargetY = targetY;
mCallback = callback;
}
@Override
public void execute() {
try {
mNativeViewHierarchyManager.measure(mReactTag, MEASURE_BUFFER);
} catch (IllegalViewOperationException e) {
mCallback.invoke();
return;
}
// Because React coordinates are relative to root container, and measure() operates
// on screen coordinates, we need to offset values using root container location.
final float containerX = (float) MEASURE_BUFFER[0];
final float containerY = (float) MEASURE_BUFFER[1];
View view = mNativeViewHierarchyManager.getView(mReactTag);
final int touchTargetReactTag = TouchTargetHelper.findTargetTagForTouch(
mTargetX,
mTargetY,
(ViewGroup) view,
NATIVE_VIEW_BUFFER);
try {
mNativeViewHierarchyManager.measure(
NATIVE_VIEW_BUFFER[0],
MEASURE_BUFFER);
} catch (IllegalViewOperationException e) {
mCallback.invoke();
return;
}
NodeRegion region = NodeRegion.EMPTY;
boolean isNativeView = NATIVE_VIEW_BUFFER[0] == touchTargetReactTag;
if (!isNativeView) {
// NATIVE_VIEW_BUFFER[0] is a FlatViewGroup, touchTargetReactTag is the touch target and
// isn't an Android View - try to get its NodeRegion
view = mNativeViewHierarchyManager.getView(NATIVE_VIEW_BUFFER[0]);
if (view instanceof FlatViewGroup) {
region = ((FlatViewGroup) view).getNodeRegionForTag(mReactTag);
}
}
int resultTag = region == NodeRegion.EMPTY ? touchTargetReactTag : region.mTag;
float x = PixelUtil.toDIPFromPixel(region.getLeft() + MEASURE_BUFFER[0] - containerX);
float y = PixelUtil.toDIPFromPixel(region.getTop() + MEASURE_BUFFER[1] - containerY);
float width = PixelUtil.toDIPFromPixel(isNativeView ?
MEASURE_BUFFER[2] : region.getRight() - region.getLeft());
float height = PixelUtil.toDIPFromPixel(isNativeView ?
MEASURE_BUFFER[3] : region.getBottom() - region.getTop());
mCallback.invoke(resultTag, x, y, width, height);
}
}
/**
* Used to delay view manager command dispatch until after the view hierarchy is updated.
* Mirrors command operation dispatch, but is only used in Nodes for view manager commands.
*/
public final class ViewManagerCommand implements UIViewOperationQueue.UIOperation {
private final int mReactTag;
private final int mCommand;
private final @Nullable ReadableArray mArgs;
public ViewManagerCommand(
int reactTag,
int command,
@Nullable ReadableArray args) {
mReactTag = reactTag;
mCommand = command;
mArgs = args;
}
@Override
public void execute() {
mNativeViewHierarchyManager.dispatchCommand(mReactTag, mCommand, mArgs);
}
}
public FlatUIViewOperationQueue(
ReactApplicationContext reactContext,
FlatNativeViewHierarchyManager nativeViewHierarchyManager,
int minTimeLeftInFrameForNonBatchedOperationMs) {
super(reactContext, nativeViewHierarchyManager, minTimeLeftInFrameForNonBatchedOperationMs);
mNativeViewHierarchyManager = nativeViewHierarchyManager;
}
/**
* Enqueues a new UIOperation that will update DrawCommands for a View defined by reactTag.
*/
public void enqueueUpdateMountState(
int reactTag,
@Nullable DrawCommand[] drawCommands,
@Nullable AttachDetachListener[] listeners,
@Nullable NodeRegion[] nodeRegions) {
enqueueUIOperation(new UpdateMountState(
reactTag,
drawCommands,
listeners,
nodeRegions));
}
/**
* Enqueues a new UIOperation that will update DrawCommands for a View defined by reactTag.
*/
public void enqueueUpdateClippingMountState(
int reactTag,
@Nullable DrawCommand[] drawCommands,
SparseIntArray drawViewIndexMap,
float[] commandMaxBot,
float[] commandMinTop,
@Nullable AttachDetachListener[] listeners,
@Nullable NodeRegion[] nodeRegions,
float[] regionMaxBot,
float[] regionMinTop,
boolean willMountViews) {
enqueueUIOperation(new UpdateClippingMountState(
reactTag,
drawCommands,
drawViewIndexMap,
commandMaxBot,
commandMinTop,
listeners,
nodeRegions,
regionMaxBot,
regionMinTop,
willMountViews));
}
public void enqueueUpdateViewGroup(int reactTag, int[] viewsToAdd, int[] viewsToDetach) {
enqueueUIOperation(new UpdateViewGroup(reactTag, viewsToAdd, viewsToDetach));
}
/**
* Creates a new UIOperation that will update View bounds for a View defined by reactTag.
*/
public UpdateViewBounds createUpdateViewBounds(
int reactTag,
int left,
int top,
int right,
int bottom) {
return new UpdateViewBounds(reactTag, left, top, right, bottom);
}
public ViewManagerCommand createViewManagerCommand(
int reactTag,
int command,
@Nullable ReadableArray args) {
return new ViewManagerCommand(reactTag, command, args);
}
/* package */ void enqueueFlatUIOperation(UIViewOperationQueue.UIOperation operation) {
enqueueUIOperation(operation);
}
public void enqueueSetPadding(
int reactTag,
int paddingLeft,
int paddingTop,
int paddingRight,
int paddingBottom) {
enqueueUIOperation(
new SetPadding(reactTag, paddingLeft, paddingTop, paddingRight, paddingBottom));
}
public void enqueueDropViews(
ArrayList<Integer> viewsToDrop,
ArrayList<Integer> parentsOfViewsToDrop) {
enqueueUIOperation(new DropViews(viewsToDrop, parentsOfViewsToDrop));
}
public void enqueueMeasureVirtualView(
int reactTag,
float scaledX,
float scaledY,
float scaledWidth,
float scaledHeight,
boolean relativeToWindow,
Callback callback) {
enqueueUIOperation(new MeasureVirtualView(
reactTag,
scaledX,
scaledY,
scaledWidth,
scaledHeight,
relativeToWindow,
callback));
}
public void enqueueProcessLayoutRequests() {
enqueueUIOperation(mProcessLayoutRequests);
}
public DetachAllChildrenFromViews enqueueDetachAllChildrenFromViews() {
DetachAllChildrenFromViews op = new DetachAllChildrenFromViews();
enqueueUIOperation(op);
return op;
}
@Override
public void enqueueFindTargetForTouch(
final int reactTag,
final float targetX,
final float targetY,
final Callback callback) {
enqueueUIOperation(
new FindTargetForTouchOperation(reactTag, targetX, targetY, callback));
}
}

View File

@ -1,29 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.ViewGroupManager;
abstract class FlatViewManager extends ViewGroupManager<FlatViewGroup> {
@Override
protected FlatViewGroup createViewInstance(ThemedReactContext reactContext) {
return new FlatViewGroup(reactContext);
}
@Override
public void setBackgroundColor(FlatViewGroup view, int backgroundColor) {
// suppress
}
@Override
public void removeAllViews(FlatViewGroup parent) {
parent.removeAllViewsInLayout();
}
}

View File

@ -1,207 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import javax.annotation.Nullable;
import android.graphics.Color;
import android.graphics.Typeface;
import android.text.TextPaint;
import android.text.style.MetricAffectingSpan;
/* package */ final class FontStylingSpan extends MetricAffectingSpan {
/* package */ static final FontStylingSpan INSTANCE = new FontStylingSpan(
Color.BLACK /* mTextColor */,
0 /* mBackgroundColor */,
-1 /* mFontSize */,
-1 /* mFontStyle */,
-1 /* mFontWeight */,
false /* mHasUnderline */,
false /* mHasStrikeThrough */,
null /* mFontFamily */,
true /* mFrozen */);
// text property
private double mTextColor;
private int mBackgroundColor;
private boolean mHasUnderline;
private boolean mHasStrikeThrough;
// font properties
private int mFontSize;
private int mFontStyle;
private int mFontWeight;
private @Nullable String mFontFamily;
// whether or not mutation is allowed.
private boolean mFrozen;
FontStylingSpan() {
}
private FontStylingSpan(
double textColor,
int backgroundColor,
int fontSize,
int fontStyle,
int fontWeight,
boolean hasUnderline,
boolean hasStrikeThrough,
@Nullable String fontFamily,
boolean frozen) {
mTextColor = textColor;
mBackgroundColor = backgroundColor;
mFontSize = fontSize;
mFontStyle = fontStyle;
mFontWeight = fontWeight;
mHasUnderline = hasUnderline;
mHasStrikeThrough = hasStrikeThrough;
mFontFamily = fontFamily;
mFrozen = frozen;
}
/* package */ FontStylingSpan mutableCopy() {
return new FontStylingSpan(
mTextColor,
mBackgroundColor,
mFontSize,
mFontStyle,
mFontWeight,
mHasUnderline,
mHasStrikeThrough,
mFontFamily,
false);
}
/* package */ boolean isFrozen() {
return mFrozen;
}
/* package */ void freeze() {
mFrozen = true;
}
/* package */ double getTextColor() {
return mTextColor;
}
/* package */ void setTextColor(double textColor) {
mTextColor = textColor;
}
/* package */ int getBackgroundColor() {
return mBackgroundColor;
}
/* package */ void setBackgroundColor(int backgroundColor) {
mBackgroundColor = backgroundColor;
}
/* package */ int getFontSize() {
return mFontSize;
}
/* package */ void setFontSize(int fontSize) {
mFontSize = fontSize;
}
/* package */ int getFontStyle() {
return mFontStyle;
}
/* package */ void setFontStyle(int fontStyle) {
mFontStyle = fontStyle;
}
/* package */ int getFontWeight() {
return mFontWeight;
}
/* package */ void setFontWeight(int fontWeight) {
mFontWeight = fontWeight;
}
/* package */ @Nullable String getFontFamily() {
return mFontFamily;
}
/* package */ void setFontFamily(@Nullable String fontFamily) {
mFontFamily = fontFamily;
}
/* package */ boolean hasUnderline() {
return mHasUnderline;
}
/* package */ void setHasUnderline(boolean hasUnderline) {
mHasUnderline = hasUnderline;
}
/* package */ boolean hasStrikeThrough() {
return mHasStrikeThrough;
}
/* package */ void setHasStrikeThrough(boolean hasStrikeThrough) {
mHasStrikeThrough = hasStrikeThrough;
}
@Override
public void updateDrawState(TextPaint ds) {
if (!Double.isNaN(mTextColor)) {
ds.setColor((int) mTextColor);
}
ds.bgColor = mBackgroundColor;
ds.setUnderlineText(mHasUnderline);
ds.setStrikeThruText(mHasStrikeThrough);
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;
}
if (mFontFamily != null) {
typeface = TypefaceCache.getTypeface(mFontFamily, newStyle);
} else {
typeface = TypefaceCache.getTypeface(typeface, newStyle);
}
ds.setTypeface(typeface);
}
}

View File

@ -1,56 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import android.graphics.Rect;
/**
* NodeRegion that has a hit slop.
*/
/* package */ final class HitSlopNodeRegion extends NodeRegion {
private final Rect mHitSlop;
HitSlopNodeRegion(
Rect hitSlop,
float left,
float top,
float right,
float bottom,
int tag,
boolean isVirtual) {
super(left, top, right, bottom, tag, isVirtual);
mHitSlop = hitSlop;
}
@Override
/* package */ float getTouchableLeft() {
return getLeft() - mHitSlop.left;
}
@Override
/* package */ float getTouchableTop() {
return getTop() - mHitSlop.top;
}
@Override
/* package */ float getTouchableRight() {
return getRight() + mHitSlop.right;
}
@Override
/* package */ float getTouchableBottom() {
return getBottom() + mHitSlop.bottom;
}
@Override
/* package */ boolean withinBounds(float touchX, float touchY) {
return getTouchableLeft() <= touchX && touchX < getTouchableRight() &&
getTouchableTop() <= touchY && touchY < getTouchableBottom();
}
}

View File

@ -1,124 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import java.util.Arrays;
import android.util.SparseIntArray;
/**
* {@link DrawCommandManager} with horizontal clipping (The view scrolls left and right).
*/
/* package */ final class HorizontalDrawCommandManager extends ClippingDrawCommandManager {
/* package */ HorizontalDrawCommandManager(
FlatViewGroup flatViewGroup,
DrawCommand[] drawCommands) {
super(flatViewGroup, drawCommands);
}
@Override
int commandStartIndex() {
int start = Arrays.binarySearch(mCommandMaxBottom, mClippingRect.left);
// We don't care whether we matched or not, but positive indices are helpful. The binary search
// returns ~index in the case that it isn't a match, so reverse that here.
return start < 0 ? ~start : start;
}
@Override
int commandStopIndex(int start) {
int stop = Arrays.binarySearch(
mCommandMinTop,
start,
mCommandMinTop.length,
mClippingRect.right);
// We don't care whether we matched or not, but positive indices are helpful. The binary search
// returns ~index in the case that it isn't a match, so reverse that here.
return stop < 0 ? ~stop : stop;
}
@Override
int regionStopIndex(float touchX, float touchY) {
int stop = Arrays.binarySearch(mRegionMinTop, touchX + 0.0001f);
// We don't care whether we matched or not, but positive indices are helpful. The binary search
// returns ~index in the case that it isn't a match, so reverse that here.
return stop < 0 ? ~stop : stop;
}
@Override
boolean regionAboveTouch(int index, float touchX, float touchY) {
return mRegionMaxBottom[index] < touchX;
}
/**
* Populates the max and min arrays for a given set of node regions.
*
* This should never be called from the UI thread, as the reason it exists is to do work off the
* UI thread.
*
* @param regions The regions that will eventually be mounted.
* @param maxRight At each index i, the maximum right value of all regions at or below i.
* @param minLeft At each index i, the minimum left value of all regions at or below i.
*/
public static void fillMaxMinArrays(NodeRegion[] regions, float[] maxRight, float[] minLeft) {
float last = 0;
for (int i = 0; i < regions.length; i++) {
last = Math.max(last, regions[i].getTouchableRight());
maxRight[i] = last;
}
for (int i = regions.length - 1; i >= 0; i--) {
last = Math.min(last, regions[i].getTouchableLeft());
minLeft[i] = last;
}
}
/**
* Populates the max and min arrays for a given set of draw commands. Also populates a mapping of
* react tags to their index position in the command array.
*
* This should never be called from the UI thread, as the reason it exists is to do work off the
* UI thread.
*
* @param commands The draw commands that will eventually be mounted.
* @param maxRight At each index i, the maximum right value of all draw commands at or below i.
* @param minLeft At each index i, the minimum left value of all draw commands at or below i.
* @param drawViewIndexMap Mapping of ids to index position within the draw command array.
*/
public static void fillMaxMinArrays(
DrawCommand[] commands,
float[] maxRight,
float[] minLeft,
SparseIntArray drawViewIndexMap) {
float last = 0;
// Loop through the DrawCommands, keeping track of the maximum we've seen if we only iterated
// through items up to this position.
for (int i = 0; i < commands.length; i++) {
if (commands[i] instanceof DrawView) {
DrawView drawView = (DrawView) commands[i];
// These will generally be roughly sorted by id, so try to insert at the end if possible.
drawViewIndexMap.append(drawView.reactTag, i);
last = Math.max(last, drawView.mLogicalRight);
} else {
last = Math.max(last, commands[i].getRight());
}
maxRight[i] = last;
}
// Intentionally leave last as it was, since it's at the maximum bottom position we've seen so
// far, we can use it again.
// Loop through backwards, keeping track of the minimum we've seen at this position.
for (int i = commands.length - 1; i >= 0; i--) {
if (commands[i] instanceof DrawView) {
last = Math.min(last, ((DrawView) commands[i]).mLogicalLeft);
} else {
last = Math.min(last, commands[i].getLeft());
}
minLeft[i] = last;
}
}
}

View File

@ -1,164 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import javax.annotation.Nullable;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.text.style.ReplacementSpan;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.infer.annotation.Assertions;
/* package */ final class InlineImageSpanWithPipeline extends ReplacementSpan
implements AttachDetachListener, BitmapUpdateListener {
private static final RectF TMP_RECT = new RectF();
private @Nullable PipelineRequestHelper mRequestHelper;
private @Nullable FlatViewGroup.InvalidateCallback mCallback;
private float mWidth;
private float mHeight;
private boolean mFrozen;
/* package */ InlineImageSpanWithPipeline() {
this(null, Float.NaN, Float.NaN);
}
private InlineImageSpanWithPipeline(
@Nullable PipelineRequestHelper requestHelper,
float width,
float height) {
mRequestHelper = requestHelper;
mWidth = width;
mHeight = height;
}
/* package */ InlineImageSpanWithPipeline mutableCopy() {
return new InlineImageSpanWithPipeline(mRequestHelper, mWidth, mHeight);
}
/* package */ boolean hasImageRequest() {
return mRequestHelper != null;
}
/**
* Assigns a new image request to the DrawImage, or null to clear the image request.
*/
/* package */ void setImageRequest(@Nullable ImageRequest imageRequest) {
if (imageRequest == null) {
mRequestHelper = null;
} else {
mRequestHelper = new PipelineRequestHelper(imageRequest);
}
}
/* package */ float getWidth() {
return mWidth;
}
/* package */ void setWidth(float width) {
mWidth = width;
}
/* package */ float getHeight() {
return mHeight;
}
/* package */ void setHeight(float height) {
mHeight = height;
}
/* package */ void freeze() {
mFrozen = true;
}
/* package */ boolean isFrozen() {
return mFrozen;
}
@Override
public void onSecondaryAttach(Bitmap bitmap) {
// We don't know if width or height changed, so invalidate just in case.
Assertions.assumeNotNull(mCallback).invalidate();
}
@Override
public void onBitmapReady(Bitmap bitmap) {
// Bitmap is now ready, draw it.
Assertions.assumeNotNull(mCallback).invalidate();
}
@Override
public void onImageLoadEvent(int imageLoadEvent) {
// ignore
}
@Override
public void onAttached(FlatViewGroup.InvalidateCallback callback) {
mCallback = callback;
if (mRequestHelper != null) {
mRequestHelper.attach(this);
}
}
@Override
public void onDetached() {
if (mRequestHelper != null) {
mRequestHelper.detach();
if (mRequestHelper.isDetached()) {
// optional
mCallback = null;
}
}
}
@Override
public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
if (fm != null) {
fm.ascent = -Math.round(mHeight);
fm.descent = 0;
fm.top = fm.ascent;
fm.bottom = 0;
}
return Math.round(mWidth);
}
@Override
public void draw(
Canvas canvas,
CharSequence text,
int start,
int end,
float x,
int top,
int y,
int bottom,
Paint paint) {
if (mRequestHelper == null) {
return;
}
Bitmap bitmap = mRequestHelper.getBitmap();
if (bitmap == null) {
return;
}
float bottomFloat = (float) bottom - paint.getFontMetricsInt().descent;
TMP_RECT.set(x, bottomFloat - mHeight, x + mWidth, bottomFloat);
canvas.drawBitmap(bitmap, null, TMP_RECT, paint);
}
}

View File

@ -1,168 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.uimanager.ReactShadowNode;
import com.facebook.react.uimanager.ReactShadowNodeImpl;
import javax.annotation.Nullable;
/**
* Helper class that sorts moveFrom/moveTo arrays in lockstep.
*/
/* package */ final class MoveProxy {
private @Nullable ReadableArray mMoveTo;
private int mSize;
private int[] mMapping = new int[8];
private ReactShadowNode[] mChildren = new ReactShadowNodeImpl[4];
/**
* Returns size of underlying moveTo/moveFrom arrays
*/
public int size() {
return mSize;
}
/**
* Assigns ith child that we want to move if moveFrom was sorted.
*/
public void setChildMoveFrom(int moveFromIndex, ReactShadowNode node) {
mChildren[moveFromToIndex(moveFromIndex)] = node;
}
/**
* Returns ith child that we want to move if moveTo was sorted.
*/
public ReactShadowNode getChildMoveTo(int moveToIndex) {
return mChildren[moveToToIndex(moveToIndex)];
}
/**
* Returns index of the ith child that we want to move if moveFrom was sorted
*/
public int getMoveFrom(int moveFromIndex) {
return moveFromToValue(moveFromIndex);
}
/**
* Returns index of the ith child that we want to move to if moveTo was sorted
*/
public int getMoveTo(int moveToIndex) {
return moveToToValue(moveToIndex);
}
/**
* Initialize MoveProxy with given moveFrom and moveTo arrays.
*/
public void setup(ReadableArray moveFrom, ReadableArray moveTo) {
mMoveTo = moveTo;
if (moveFrom == null) {
setSize(0);
return;
}
int size = moveFrom.size();
int requiredSpace = size + size;
if (mMapping.length < requiredSpace) {
mMapping = new int[requiredSpace];
mChildren = new FlatShadowNode[size];
}
setSize(size);
// Array contains data in the following way:
// [ k0, v0, k1, v1, k2, v2, ... ]
//
// where vi = moveFrom.getInt(ki)
// We don't technically *need* to store vi, but they are accessed so often that it makes sense
// to cache it instead of calling ReadableArray.getInt() all the time.
// Sorting algorithm will reorder ki/vi pairs in such a way that vi < v(i+1)
// Code below is an insertion sort, adapted from DualPivotQuicksort.doSort()
// At each step i, we got the following data:
// [k0, v0, k1, v2, .. k(i-1), v(i-1), unused...]
// where v0 < v1 < v2 ... < v(i-1)
//
// This holds true for step i = 0 (array of size one is sorted)
// Again, k0 = 0, v0 = moveFrom.getInt(k0)
setKeyValue(0, 0, moveFrom.getInt(0));
// At each of the next steps, we grab a new key and walk back until we find first key that is
// less than current, shifting key/value pairs if they are larger than current key.
for (int i = 1; i < size; i++) {
// this is our next key
int current = moveFrom.getInt(i);
// this loop will find correct position for it
int j;
// At this point, array is like this: [ k0, v0, k1, v1, k2, v2, ..., k(i-1), v(i-1), ... ]
for (j = i - 1; j >= 0; j--) {
if (moveFromToValue(j) < current) {
break;
}
// value at index j is < current value, shift that value and its key
setKeyValue(j + 1, moveFromToIndex(j), moveFromToValue(j));
}
setKeyValue(j + 1, i, current);
}
}
/**
* Returns index of ith key in array.
*/
private static int k(int i) {
return i * 2;
}
/**
* Returns index of ith value in array.
*/
private static int v(int i) {
return i * 2 + 1;
}
private void setKeyValue(int index, int key, int value) {
mMapping[k(index)] = key;
mMapping[v(index)] = value;
}
private int moveFromToIndex(int index) {
return mMapping[k(index)];
}
private int moveFromToValue(int index) {
return mMapping[v(index)];
}
private static int moveToToIndex(int index) {
return index;
}
private int moveToToValue(int index) {
return Assertions.assumeNotNull(mMoveTo).getInt(index);
}
private void setSize(int newSize) {
// reset references to null when shrinking to avoid memory leaks
for (int i = newSize; i < mSize; ++i) {
mChildren[i] = null;
}
mSize = newSize;
}
}

View File

@ -1,130 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import com.facebook.react.uimanager.ReactShadowNode;
import com.facebook.react.uimanager.ReactShadowNodeImpl;
import com.facebook.react.uimanager.ReactStylesDiffMap;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.UIViewOperationQueue;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.ViewManager;
import com.facebook.yoga.YogaMeasureFunction;
import com.facebook.yoga.YogaUnit;
import com.facebook.yoga.YogaValue;
import javax.annotation.Nullable;
/* package */ final class NativeViewWrapper extends FlatShadowNode implements AndroidView {
@Nullable
private final ReactShadowNode mReactShadowNode;
private final boolean mNeedsCustomLayoutForChildren;
private boolean mPaddingChanged = false;
private boolean mForceMountGrandChildrenToView;
/* package */ NativeViewWrapper(ViewManager viewManager) {
ReactShadowNode reactShadowNode = viewManager.createShadowNodeInstance();
if (reactShadowNode instanceof YogaMeasureFunction) {
mReactShadowNode = reactShadowNode;
setMeasureFunction((YogaMeasureFunction) reactShadowNode);
} else {
mReactShadowNode = null;
}
if (viewManager instanceof ViewGroupManager) {
ViewGroupManager viewGroupManager = (ViewGroupManager) viewManager;
mNeedsCustomLayoutForChildren = viewGroupManager.needsCustomLayoutForChildren();
mForceMountGrandChildrenToView = viewGroupManager.shouldPromoteGrandchildren();
} else {
mNeedsCustomLayoutForChildren = false;
}
forceMountToView();
forceMountChildrenToView();
}
@Override
public boolean needsCustomLayoutForChildren() {
return mNeedsCustomLayoutForChildren;
}
@Override
public boolean isPaddingChanged() {
return mPaddingChanged;
}
@Override
public void resetPaddingChanged() {
mPaddingChanged = false;
}
@Override
public void setBackgroundColor(int backgroundColor) {
// suppress, this is handled by a ViewManager
}
@Override
public void setReactTag(int reactTag) {
super.setReactTag(reactTag);
if (mReactShadowNode != null) {
mReactShadowNode.setReactTag(reactTag);
}
}
@Override
public void setThemedContext(ThemedReactContext themedContext) {
super.setThemedContext(themedContext);
if (mReactShadowNode != null) {
mReactShadowNode.setThemedContext(themedContext);
}
}
@Override
/* package*/ void handleUpdateProperties(ReactStylesDiffMap styles) {
if (mReactShadowNode != null) {
mReactShadowNode.updateProperties(styles);
}
}
@Override
public void addChildAt(ReactShadowNodeImpl child, int i) {
super.addChildAt(child, i);
if (mForceMountGrandChildrenToView && child instanceof FlatShadowNode) {
((FlatShadowNode) child).forceMountChildrenToView();
}
}
@Override
public void setPadding(int spacingType, float padding) {
YogaValue current = getStylePadding(spacingType);
if (current.unit != YogaUnit.POINT || current.value != padding) {
super.setPadding(spacingType, padding);
mPaddingChanged = true;
markUpdated();
}
}
@Override
public void setPaddingPercent(int spacingType, float percent) {
YogaValue current = getStylePadding(spacingType);
if (current.unit != YogaUnit.PERCENT || current.value != percent) {
super.setPadding(spacingType, percent);
mPaddingChanged = true;
markUpdated();
}
}
@Override
public void onCollectExtraUpdates(UIViewOperationQueue uiViewOperationQueue) {
if (mReactShadowNode != null && mReactShadowNode.hasUnseenUpdates()) {
mReactShadowNode.onCollectExtraUpdates(uiViewOperationQueue);
markUpdateSeen();
}
}
}

View File

@ -1,133 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
/* package */ class NodeRegion {
/* package */ static final NodeRegion[] EMPTY_ARRAY = new NodeRegion[0];
/* package */ static final NodeRegion EMPTY = new NodeRegion(0, 0, 0, 0, -1, false);
private final float mLeft;
private final float mTop;
private final float mRight;
private final float mBottom;
/* package */ final int mTag;
/* package */ final boolean mIsVirtual;
/* package */ NodeRegion(
float left,
float top,
float right,
float bottom,
int tag,
boolean isVirtual) {
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
mTag = tag;
mIsVirtual = isVirtual;
}
/* package */ final boolean matches(
float left,
float top,
float right,
float bottom,
boolean isVirtual) {
return left == mLeft && top == mTop && right == mRight && bottom == mBottom &&
isVirtual == mIsVirtual;
}
/**
* The left bound of the underlying node.
*
* @return The node bound.
*/
/* package */ final float getLeft() {
return mLeft;
}
/**
* The top bound of the underlying node.
*
* @return The node bound.
*/
/* package */ final float getTop() {
return mTop;
}
/**
* The right bound of the underlying node.
*
* @return The node bound.
*/
/* package */ final float getRight() {
return mRight;
}
/**
* The bottom bound of the underlying node.
*
* @return The node bound.
*/
/* package */ final float getBottom() {
return mBottom;
}
/**
* The left bound of the region for the purpose of touch. This is usually the bound of the
* underlying node, except in the case of hit slop.
*
* @return The touch bound.
*/
/* package */ float getTouchableLeft() {
return getLeft();
}
/**
* The top bound of the region for the purpose of touch. This is usually the bound of the
* underlying node, except in the case of hit slop.
*
* @return The touch bound.
*/
/* package */ float getTouchableTop() {
return getTop();
}
/**
* The right bound of the region for the purpose of touch. This is usually the bound of the
* underlying node, except in the case of hit slop.
*
* @return The touch bound.
*/
/* package */ float getTouchableRight() {
return getRight();
}
/**
* The bottom bound of the region for the purpose of touch. This is usually the bound of the
* underlying node, except in the case of hit slop.
*
* @return The touch bound.
*/
/* package */ float getTouchableBottom() {
return getBottom();
}
/* package */ boolean withinBounds(float touchX, float touchY) {
return mLeft <= touchX && touchX < mRight && mTop <= touchY && touchY < mBottom;
}
/* package */ int getReactTag(float touchX, float touchY) {
return mTag;
}
/* package */ boolean matchesTag(int tag) {
return mTag == tag;
}
}

View File

@ -1,186 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import javax.annotation.Nullable;
import android.graphics.Bitmap;
import com.facebook.common.executors.UiThreadImmediateExecutorService;
import com.facebook.common.references.CloseableReference;
import com.facebook.datasource.DataSource;
import com.facebook.datasource.DataSubscriber;
import com.facebook.imagepipeline.core.ImagePipeline;
import com.facebook.imagepipeline.core.ImagePipelineFactory;
import com.facebook.imagepipeline.image.CloseableBitmap;
import com.facebook.imagepipeline.image.CloseableImage;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.views.image.ImageLoadEvent;
/**
* Helper class for DrawImage that helps manage fetch requests through ImagePipeline.
*
* Request states this class can be in:
* 1) mDataSource == null, mImageRef == null : request has not be started, was canceled or failed.
* 2) mDataSource != null, mImageRef == null : request is in progress.
* 3) mDataSource == null, mImageRef != null : request successfully finished.
* 4) mDataSource != null, mImageRef != null : invalid state (should never happen)
*/
/* package */ final class PipelineRequestHelper
implements DataSubscriber<CloseableReference<CloseableImage>> {
private final ImageRequest mImageRequest;
private @Nullable BitmapUpdateListener mBitmapUpdateListener;
private @Nullable DataSource<CloseableReference<CloseableImage>> mDataSource;
private @Nullable CloseableReference<CloseableImage> mImageRef;
private int mAttachCounter;
/* package */ PipelineRequestHelper(ImageRequest imageRequest) {
mImageRequest = imageRequest;
}
/* package */ void attach(BitmapUpdateListener listener) {
mBitmapUpdateListener = listener;
mAttachCounter++;
if (mAttachCounter != 1) {
// this is a secondary attach, ignore it, only updating Bitmap boundaries if needed.
Bitmap bitmap = getBitmap();
if (bitmap != null) {
listener.onSecondaryAttach(bitmap);
}
return;
}
listener.onImageLoadEvent(ImageLoadEvent.ON_LOAD_START);
Assertions.assertCondition(mDataSource == null);
Assertions.assertCondition(mImageRef == null);
// Submit the request
ImagePipeline imagePipeline = ImagePipelineFactory.getInstance().getImagePipeline();
mDataSource = imagePipeline.fetchDecodedImage(mImageRequest, RCTImageView.getCallerContext());
mDataSource.subscribe(this, UiThreadImmediateExecutorService.getInstance());
}
/**
* Returns whether detach() was primary, false otherwise.
*/
/* package */ void detach() {
--mAttachCounter;
if (mAttachCounter != 0) {
// this is a secondary detach, ignore it
return;
}
if (mDataSource != null) {
mDataSource.close();
mDataSource = null;
}
if (mImageRef != null) {
mImageRef.close();
mImageRef = null;
}
mBitmapUpdateListener = null;
}
/**
* Returns an unsafe bitmap reference. Do not assign the result of this method to anything other
* than a local variable, or it will no longer work with the reference count goes to zero.
*/
/* package */ @Nullable Bitmap getBitmap() {
if (mImageRef == null) {
return null;
}
CloseableImage closeableImage = mImageRef.get();
if (!(closeableImage instanceof CloseableBitmap)) {
mImageRef.close();
mImageRef = null;
return null;
}
return ((CloseableBitmap) closeableImage).getUnderlyingBitmap();
}
/* package */ boolean isDetached() {
return mAttachCounter == 0;
}
@Override
public void onNewResult(DataSource<CloseableReference<CloseableImage>> dataSource) {
if (!dataSource.isFinished()) {
// only interested in final image, no need to close the dataSource
return;
}
try {
if (mDataSource != dataSource) {
// Shouldn't ever happen, but let's be safe (dataSource got closed by callback still fired?)
return;
}
mDataSource = null;
CloseableReference<CloseableImage> imageReference = dataSource.getResult();
if (imageReference == null) {
// Shouldn't ever happen, but let's be safe (dataSource got closed by callback still fired?)
return;
}
CloseableImage image = imageReference.get();
if (!(image instanceof CloseableBitmap)) {
// only bitmaps are supported
imageReference.close();
return;
}
mImageRef = imageReference;
Bitmap bitmap = getBitmap();
if (bitmap == null) {
// Shouldn't ever happen, but let's be safe.
return;
}
BitmapUpdateListener listener = Assertions.assumeNotNull(mBitmapUpdateListener);
listener.onBitmapReady(bitmap);
listener.onImageLoadEvent(ImageLoadEvent.ON_LOAD);
listener.onImageLoadEvent(ImageLoadEvent.ON_LOAD_END);
} finally {
dataSource.close();
}
}
@Override
public void onFailure(DataSource<CloseableReference<CloseableImage>> dataSource) {
if (mDataSource == dataSource) {
Assertions.assumeNotNull(mBitmapUpdateListener).onImageLoadEvent(ImageLoadEvent.ON_ERROR);
Assertions.assumeNotNull(mBitmapUpdateListener).onImageLoadEvent(ImageLoadEvent.ON_LOAD_END);
mDataSource = null;
}
dataSource.close();
}
@Override
public void onCancellation(DataSource<CloseableReference<CloseableImage>> dataSource) {
if (mDataSource == dataSource) {
mDataSource = null;
}
dataSource.close();
}
@Override
public void onProgressUpdate(DataSource<CloseableReference<CloseableImage>> dataSource) {
}
}

View File

@ -1,151 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import javax.annotation.Nullable;
import com.facebook.drawee.drawable.ScalingUtils.ScaleType;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.uimanager.PixelUtil;
import com.facebook.react.uimanager.Spacing;
import com.facebook.react.uimanager.ViewProps;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.views.image.ImageResizeMode;
/**
* RCTImageView is a top-level node for Image. It can display either a remote image
* (source must start with http:// or https://) or a local resource (a BitmapDrawable).
*/
/* package */ class RCTImageView<T extends AbstractDrawCommand & DrawImage> extends FlatShadowNode {
static Object sCallerContext = RCTImageView.class;
/**
* Assigns a CallerContext to execute network requests with.
*/
/* package */ static void setCallerContext(Object callerContext) {
sCallerContext = callerContext;
}
/* package */ static Object getCallerContext() {
return sCallerContext;
}
private T mDrawImage;
/* package */ RCTImageView(T drawImage) {
mDrawImage = drawImage;
}
@Override
protected void collectState(
StateBuilder stateBuilder,
float left,
float top,
float right,
float bottom,
float clipLeft,
float clipTop,
float clipRight,
float clipBottom) {
super.collectState(
stateBuilder,
left,
top,
right,
bottom,
clipLeft,
clipTop,
clipRight,
clipBottom);
if (mDrawImage.hasImageRequest()) {
mDrawImage = (T) mDrawImage.updateBoundsAndFreeze(
left,
top,
right,
bottom,
clipLeft,
clipTop,
clipRight,
clipBottom);
stateBuilder.addDrawCommand(mDrawImage);
stateBuilder.addAttachDetachListener(mDrawImage);
}
}
@Override
boolean doesDraw() {
return mDrawImage.hasImageRequest() || super.doesDraw();
}
@ReactProp(name = "shouldNotifyLoadEvents")
public void setShouldNotifyLoadEvents(boolean shouldNotifyLoadEvents) {
getMutableDrawImage().setReactTag(shouldNotifyLoadEvents ? getReactTag() : 0);
}
@ReactProp(name = "src")
public void setSource(@Nullable ReadableArray sources) {
getMutableDrawImage().setSource(getThemedContext(), sources);
}
@ReactProp(name = "tintColor")
public void setTintColor(int tintColor) {
getMutableDrawImage().setTintColor(tintColor);
}
@ReactProp(name = ViewProps.RESIZE_MODE)
public void setResizeMode(@Nullable String resizeMode) {
ScaleType scaleType = ImageResizeMode.toScaleType(resizeMode);
if (mDrawImage.getScaleType() != scaleType) {
getMutableDrawImage().setScaleType(scaleType);
}
}
@ReactProp(name = "borderColor", customType = "Color")
public void setBorderColor(int borderColor) {
if (mDrawImage.getBorderColor() != borderColor) {
getMutableDrawImage().setBorderColor(borderColor);
}
}
@Override
public void setBorder(int spacingType, float borderWidth) {
super.setBorder(spacingType, borderWidth);
if (spacingType == Spacing.ALL && mDrawImage.getBorderWidth() != borderWidth) {
getMutableDrawImage().setBorderWidth(borderWidth);
}
}
@ReactProp(name = "borderRadius")
public void setBorderRadius(float borderRadius) {
if (mDrawImage.getBorderRadius() != borderRadius) {
getMutableDrawImage().setBorderRadius(PixelUtil.toPixelFromDIP(borderRadius));
}
}
@ReactProp(name = "fadeDuration")
public void setFadeDuration(int durationMs) {
getMutableDrawImage().setFadeDuration(durationMs);
}
@ReactProp(name = "progressiveRenderingEnabled")
public void setProgressiveRenderingEnabled(boolean enabled) {
getMutableDrawImage().setProgressiveRenderingEnabled(enabled);
}
private T getMutableDrawImage() {
if (mDrawImage.isFrozen()) {
mDrawImage = (T) mDrawImage.mutableCopy();
invalidate();
}
return mDrawImage;
}
}

View File

@ -1,67 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.drawee.controller.AbstractDraweeControllerBuilder;
import com.facebook.react.views.image.GlobalImageLoadListener;
import javax.annotation.Nullable;
public final class RCTImageViewManager extends FlatViewManager {
/* package */ static final String REACT_CLASS = "RCTImageView";
private @Nullable AbstractDraweeControllerBuilder mDraweeControllerBuilder;
private @Nullable GlobalImageLoadListener mGlobalImageLoadListener;
private final @Nullable Object mCallerContext;
public RCTImageViewManager() {
this(null, null);
}
public RCTImageViewManager(
AbstractDraweeControllerBuilder draweeControllerBuilder,
Object callerContext) {
this(draweeControllerBuilder, null, callerContext);
}
public RCTImageViewManager(
AbstractDraweeControllerBuilder draweeControllerBuilder,
@Nullable GlobalImageLoadListener globalImageLoadListener,
Object callerContext) {
mDraweeControllerBuilder = draweeControllerBuilder;
mGlobalImageLoadListener = globalImageLoadListener;
mCallerContext = callerContext;
}
@Override
public String getName() {
return REACT_CLASS;
}
@Override
public RCTImageView createShadowNodeInstance() {
return new RCTImageView(new DrawImageWithDrawee(mGlobalImageLoadListener));
}
@Override
public Class<RCTImageView> getShadowNodeClass() {
return RCTImageView.class;
}
public AbstractDraweeControllerBuilder getDraweeControllerBuilder() {
if (mDraweeControllerBuilder == null) {
mDraweeControllerBuilder = Fresco.newDraweeControllerBuilder();
}
return mDraweeControllerBuilder;
}
public Object getCallerContext() {
return mCallerContext;
}
}

View File

@ -1,26 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import com.facebook.react.uimanager.LayoutShadowNode;
import com.facebook.react.views.modal.ReactModalHostManager;
public class RCTModalHostManager extends ReactModalHostManager {
/* package */ static final String REACT_CLASS = ReactModalHostManager.REACT_CLASS;
@Override
public LayoutShadowNode createShadowNodeInstance() {
return new FlatReactModalShadowNode();
}
@Override
public Class<? extends LayoutShadowNode> getShadowNodeClass() {
return FlatReactModalShadowNode.class;
}
}

View File

@ -1,54 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import javax.annotation.Nullable;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import com.facebook.react.uimanager.annotations.ReactProp;
/**
* RCTRawText is a FlatTextShadowNode that can only contain raw text (but not styling).
*/
/* package */ final class RCTRawText extends FlatTextShadowNode {
private @Nullable String mText;
@Override
protected void performCollectText(SpannableStringBuilder builder) {
if (mText != null) {
builder.append(mText);
}
}
@Override
protected void performApplySpans(
SpannableStringBuilder builder,
int begin,
int end,
boolean isEditable) {
builder.setSpan(
this,
begin,
end,
Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
}
@Override
protected void performCollectAttachDetachListeners(StateBuilder stateBuilder) {
// nothing to do
}
@ReactProp(name = "text")
public void setText(@Nullable String text) {
mText = text;
notifyChanged(true);
}
}

View File

@ -1,31 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
/**
* ViewManager that creates instances of RCTRawText.
*/
public final class RCTRawTextManager extends VirtualViewManager<RCTRawText> {
/* package */ static final String REACT_CLASS = "RCTRawText";
@Override
public String getName() {
return REACT_CLASS;
}
@Override
public RCTRawText createShadowNodeInstance() {
return new RCTRawText();
}
@Override
public Class<RCTRawText> getShadowNodeClass() {
return RCTRawText.class;
}
}

View File

@ -1,360 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import javax.annotation.Nullable;
import android.support.v4.text.TextDirectionHeuristicsCompat;
import android.text.Layout;
import android.text.TextUtils;
import android.view.Gravity;
import com.facebook.yoga.YogaDirection;
import com.facebook.yoga.YogaMeasureMode;
import com.facebook.yoga.YogaMeasureFunction;
import com.facebook.yoga.YogaNode;
import com.facebook.yoga.YogaMeasureOutput;
import com.facebook.fbui.textlayoutbuilder.TextLayoutBuilder;
import com.facebook.fbui.textlayoutbuilder.glyphwarmer.GlyphWarmerImpl;
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
import com.facebook.react.uimanager.PixelUtil;
import com.facebook.react.uimanager.Spacing;
import com.facebook.react.uimanager.ViewDefaults;
import com.facebook.react.uimanager.ViewProps;
import com.facebook.react.uimanager.annotations.ReactProp;
/**
* 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 YogaMeasureFunction {
// index of left and right in the Layout.Alignment enum since the base values are @hide
private static final int ALIGNMENT_LEFT = 3;
private static final int ALIGNMENT_RIGHT = 4;
// We set every value we use every time we use the layout builder, so we can get away with only
// using a single instance.
private static final TextLayoutBuilder sTextLayoutBuilder =
new TextLayoutBuilder()
.setShouldCacheLayout(false)
.setShouldWarmText(true)
.setGlyphWarmer(new GlyphWarmerImpl());
private @Nullable CharSequence mText;
private @Nullable DrawTextLayout mDrawCommand;
private float mSpacingMult = 1.0f;
private float mSpacingAdd = 0.0f;
private int mNumberOfLines = Integer.MAX_VALUE;
private int mAlignment = Gravity.NO_GRAVITY;
private boolean mIncludeFontPadding = true;
public RCTText() {
setMeasureFunction(this);
getSpan().setFontSize(getDefaultFontSize());
}
@Override
public boolean isVirtual() {
return false;
}
@Override
public boolean isVirtualAnchor() {
return true;
}
@Override
public long measure(
YogaNode node,
float width,
YogaMeasureMode widthMode,
float height,
YogaMeasureMode heightMode) {
CharSequence text = getText();
if (TextUtils.isEmpty(text)) {
// to indicate that we don't have anything to display
mText = null;
return YogaMeasureOutput.make(0, 0);
} else {
mText = text;
}
Layout layout = createTextLayout(
(int) Math.ceil(width),
widthMode,
TextUtils.TruncateAt.END,
mIncludeFontPadding,
mNumberOfLines,
mNumberOfLines == 1,
text,
getFontSize(),
mSpacingAdd,
mSpacingMult,
getFontStyle(),
getAlignment());
if (mDrawCommand != null && !mDrawCommand.isFrozen()) {
mDrawCommand.setLayout(layout);
} else {
mDrawCommand = new DrawTextLayout(layout);
}
return YogaMeasureOutput.make(mDrawCommand.getLayoutWidth(), mDrawCommand.getLayoutHeight());
}
@Override
protected void collectState(
StateBuilder stateBuilder,
float left,
float top,
float right,
float bottom,
float clipLeft,
float clipTop,
float clipRight,
float clipBottom) {
super.collectState(
stateBuilder,
left,
top,
right,
bottom,
clipLeft,
clipTop,
clipRight,
clipBottom);
if (mText == null) {
// as an optimization, LayoutEngine may not call measure in certain cases, such as when the
// dimensions are already defined. in these cases, we should still draw the text.
if (bottom - top > 0 && right - left > 0) {
CharSequence text = getText();
if (!TextUtils.isEmpty(text)) {
mText = text;
}
}
if (mText == null) {
// nothing to draw (empty text).
return;
}
}
boolean updateNodeRegion = false;
if (mDrawCommand == null) {
mDrawCommand = new DrawTextLayout(createTextLayout(
(int) Math.ceil(right - left),
YogaMeasureMode.EXACTLY,
TextUtils.TruncateAt.END,
mIncludeFontPadding,
mNumberOfLines,
mNumberOfLines == 1,
mText,
getFontSize(),
mSpacingAdd,
mSpacingMult,
getFontStyle(),
getAlignment()));
updateNodeRegion = true;
}
left += getPadding(Spacing.LEFT);
top += getPadding(Spacing.TOP);
// these are actual right/bottom coordinates where this DrawCommand will draw.
right = left + mDrawCommand.getLayoutWidth();
bottom = top + mDrawCommand.getLayoutHeight();
mDrawCommand = (DrawTextLayout) mDrawCommand.updateBoundsAndFreeze(
left,
top,
right,
bottom,
clipLeft,
clipTop,
clipRight,
clipBottom);
stateBuilder.addDrawCommand(mDrawCommand);
if (updateNodeRegion) {
NodeRegion nodeRegion = getNodeRegion();
if (nodeRegion instanceof TextNodeRegion) {
((TextNodeRegion) nodeRegion).setLayout(mDrawCommand.getLayout());
}
}
performCollectAttachDetachListeners(stateBuilder);
}
@Override
boolean doesDraw() {
// assume text always draws - this is a performance optimization to avoid having to
// getText() when layout was skipped and when collectState wasn't yet called
return true;
}
@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);
}
@ReactProp(name = ViewProps.NUMBER_OF_LINES, defaultInt = Integer.MAX_VALUE)
public void setNumberOfLines(int numberOfLines) {
mNumberOfLines = numberOfLines;
notifyChanged(true);
}
@ReactProp(name = ViewProps.INCLUDE_FONT_PADDING, defaultBoolean = true)
public void setIncludeFontPadding(boolean includepad) {
mIncludeFontPadding = includepad;
}
@Override
/* package */ void updateNodeRegion(
float left,
float top,
float right,
float bottom,
boolean isVirtual) {
NodeRegion nodeRegion = getNodeRegion();
if (mDrawCommand == null) {
if (!nodeRegion.matches(left, top, right, bottom, isVirtual)) {
setNodeRegion(new TextNodeRegion(left, top, right, bottom, getReactTag(), isVirtual, null));
}
return;
}
Layout layout = null;
if (nodeRegion instanceof TextNodeRegion) {
layout = ((TextNodeRegion) nodeRegion).getLayout();
}
Layout newLayout = mDrawCommand.getLayout();
if (!nodeRegion.matches(left, top, right, bottom, isVirtual) || layout != newLayout) {
setNodeRegion(
new TextNodeRegion(left, top, right, bottom, getReactTag(), isVirtual, newLayout));
}
}
@Override
protected int getDefaultFontSize() {
// top-level <Text /> 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();
}
@ReactProp(name = ViewProps.TEXT_ALIGN)
public void setTextAlign(@Nullable String textAlign) {
if (textAlign == null || "auto".equals(textAlign)) {
mAlignment = Gravity.NO_GRAVITY;
} else if ("left".equals(textAlign)) {
// left and right may yield potentially different results (relative to non-nodes) in cases
// when supportsRTL="true" in the manifest.
mAlignment = Gravity.LEFT;
} else if ("right".equals(textAlign)) {
mAlignment = Gravity.RIGHT;
} else if ("center".equals(textAlign)) {
mAlignment = Gravity.CENTER;
} else {
throw new JSApplicationIllegalArgumentException("Invalid textAlign: " + textAlign);
}
notifyChanged(false);
}
public Layout.Alignment getAlignment() {
boolean isRtl = getLayoutDirection() == YogaDirection.RTL;
switch (mAlignment) {
// Layout.Alignment.RIGHT and Layout.Alignment.LEFT are @hide :(
case Gravity.LEFT:
int index = isRtl ? ALIGNMENT_RIGHT : ALIGNMENT_LEFT;
return Layout.Alignment.values()[index];
case Gravity.RIGHT:
index = isRtl ? ALIGNMENT_LEFT : ALIGNMENT_RIGHT;
return Layout.Alignment.values()[index];
case Gravity.CENTER:
return Layout.Alignment.ALIGN_CENTER;
case Gravity.NO_GRAVITY:
default:
return Layout.Alignment.ALIGN_NORMAL;
}
}
private static Layout createTextLayout(
int width,
YogaMeasureMode widthMode,
TextUtils.TruncateAt ellipsize,
boolean shouldIncludeFontPadding,
int maxLines,
boolean isSingleLine,
CharSequence text,
int textSize,
float extraSpacing,
float spacingMultiplier,
int textStyle,
Layout.Alignment textAlignment) {
Layout newLayout;
final @TextLayoutBuilder.MeasureMode int textMeasureMode;
switch (widthMode) {
case UNDEFINED:
textMeasureMode = TextLayoutBuilder.MEASURE_MODE_UNSPECIFIED;
break;
case EXACTLY:
textMeasureMode = TextLayoutBuilder.MEASURE_MODE_EXACTLY;
break;
case AT_MOST:
textMeasureMode = TextLayoutBuilder.MEASURE_MODE_AT_MOST;
break;
default:
throw new IllegalStateException("Unexpected size mode: " + widthMode);
}
sTextLayoutBuilder
.setEllipsize(ellipsize)
.setMaxLines(maxLines)
.setSingleLine(isSingleLine)
.setText(text)
.setTextSize(textSize)
.setWidth(width, textMeasureMode);
sTextLayoutBuilder.setTextStyle(textStyle);
sTextLayoutBuilder.setTextDirection(TextDirectionHeuristicsCompat.FIRSTSTRONG_LTR);
sTextLayoutBuilder.setIncludeFontPadding(shouldIncludeFontPadding);
sTextLayoutBuilder.setTextSpacingExtra(extraSpacing);
sTextLayoutBuilder.setTextSpacingMultiplier(spacingMultiplier);
sTextLayoutBuilder.setAlignment(textAlignment);
newLayout = sTextLayoutBuilder.build();
sTextLayoutBuilder.setText(null);
return newLayout;
}
}

View File

@ -1,88 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import javax.annotation.Nullable;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import com.facebook.imagepipeline.request.ImageRequestBuilder;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.views.imagehelper.ImageSource;
/**
* RCTTextInlineImage
*/
/* package */ class RCTTextInlineImage extends FlatTextShadowNode {
private InlineImageSpanWithPipeline mInlineImageSpan = new InlineImageSpanWithPipeline();
@Override
public void setStyleWidth(float width) {
super.setStyleWidth(width);
if (mInlineImageSpan.getWidth() != width) {
getMutableSpan().setWidth(width);
notifyChanged(true);
}
}
@Override
public void setStyleHeight(float height) {
super.setStyleHeight(height);
if (mInlineImageSpan.getHeight() != height) {
getMutableSpan().setHeight(height);
notifyChanged(true);
}
}
@Override
protected void performCollectText(SpannableStringBuilder builder) {
builder.append("I");
}
@Override
protected void performApplySpans(
SpannableStringBuilder builder,
int begin,
int end,
boolean isEditable) {
mInlineImageSpan.freeze();
builder.setSpan(
mInlineImageSpan,
begin,
end,
Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
}
@Override
protected void performCollectAttachDetachListeners(StateBuilder stateBuilder) {
// mInlineImageSpan should already be frozen so no need to freeze it again
stateBuilder.addAttachDetachListener(mInlineImageSpan);
}
@ReactProp(name = "src")
public void setSource(@Nullable ReadableArray sources) {
final String source =
(sources == null || sources.size() == 0) ? null : sources.getMap(0).getString("uri");
final ImageSource imageSource = source == null ? null :
new ImageSource(getThemedContext(), source);
getMutableSpan().setImageRequest(imageSource == null ? null :
ImageRequestBuilder.newBuilderWithSource(imageSource.getUri()).build());
}
private InlineImageSpanWithPipeline getMutableSpan() {
if (mInlineImageSpan.isFrozen()) {
mInlineImageSpan = mInlineImageSpan.mutableCopy();
}
return mInlineImageSpan;
}
}

View File

@ -1,31 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
/**
* ViewManager that creates instances of RCTTextInlineImage.
*/
public final class RCTTextInlineImageManager extends VirtualViewManager<RCTTextInlineImage> {
/* package */ static final String REACT_CLASS = "RCTTextInlineImage";
@Override
public String getName() {
return REACT_CLASS;
}
@Override
public RCTTextInlineImage createShadowNodeInstance() {
return new RCTTextInlineImage();
}
@Override
public Class<RCTTextInlineImage> getShadowNodeClass() {
return RCTTextInlineImage.class;
}
}

View File

@ -1,191 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import static com.facebook.react.views.text.ReactRawTextShadowNode.PROP_TEXT;
import static com.facebook.react.views.text.ReactTextShadowNode.UNSET;
import android.annotation.TargetApi;
import android.os.Build;
import android.text.SpannableStringBuilder;
import android.util.TypedValue;
import android.view.ViewGroup;
import android.widget.EditText;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.uimanager.PixelUtil;
import com.facebook.react.uimanager.Spacing;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.UIViewOperationQueue;
import com.facebook.react.uimanager.ViewDefaults;
import com.facebook.react.uimanager.ViewProps;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.views.text.ReactTextUpdate;
import com.facebook.react.views.view.MeasureUtil;
import com.facebook.yoga.YogaMeasureFunction;
import com.facebook.yoga.YogaMeasureMode;
import com.facebook.yoga.YogaMeasureOutput;
import com.facebook.yoga.YogaNode;
import javax.annotation.Nullable;
public class RCTTextInput extends RCTVirtualText implements AndroidView, YogaMeasureFunction {
@Nullable private String mText;
private int mJsEventCount = UNSET;
private boolean mPaddingChanged = false;
private int mNumberOfLines = UNSET;
private @Nullable EditText mEditText;
public RCTTextInput() {
forceMountToView();
setMeasureFunction(this);
}
@Override
protected void notifyChanged(boolean shouldRemeasure) {
super.notifyChanged(shouldRemeasure);
// needed to trigger onCollectExtraUpdates
markUpdated();
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
@Override
public void setThemedContext(ThemedReactContext themedContext) {
super.setThemedContext(themedContext);
mEditText = new EditText(themedContext);
// This is needed to fix an android bug since 4.4.3 which will throw an NPE in measure,
// setting the layoutParams fixes it: https://code.google.com/p/android/issues/detail?id=75877
mEditText.setLayoutParams(
new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
setDefaultPadding(Spacing.START, mEditText.getPaddingStart());
setDefaultPadding(Spacing.TOP, mEditText.getPaddingTop());
setDefaultPadding(Spacing.END, mEditText.getPaddingEnd());
setDefaultPadding(Spacing.BOTTOM, mEditText.getPaddingBottom());
mEditText.setPadding(0, 0, 0, 0);
}
@Override
public long measure(
YogaNode node,
float width,
YogaMeasureMode widthMode,
float height,
YogaMeasureMode heightMode) {
// measure() should never be called before setThemedContext()
EditText editText = Assertions.assertNotNull(mEditText);
int fontSize = getFontSize();
editText.setTextSize(
TypedValue.COMPLEX_UNIT_PX,
fontSize == UNSET ?
(int) Math.ceil(PixelUtil.toPixelFromSP(ViewDefaults.FONT_SIZE_SP)) : fontSize);
if (mNumberOfLines != UNSET) {
editText.setLines(mNumberOfLines);
}
editText.measure(
MeasureUtil.getMeasureSpec(width, widthMode),
MeasureUtil.getMeasureSpec(height, heightMode));
return YogaMeasureOutput.make(editText.getMeasuredWidth(), editText.getMeasuredHeight());
}
@Override
public boolean isVirtual() {
return false;
}
@Override
public boolean isVirtualAnchor() {
return true;
}
@Override
public void setBackgroundColor(int backgroundColor) {
// suppress, this is handled by a ViewManager
}
@Override
public void onCollectExtraUpdates(UIViewOperationQueue uiViewOperationQueue) {
super.onCollectExtraUpdates(uiViewOperationQueue);
if (mJsEventCount != UNSET) {
ReactTextUpdate reactTextUpdate =
new ReactTextUpdate(
getText(),
mJsEventCount,
false,
getPadding(Spacing.START),
getPadding(Spacing.TOP),
getPadding(Spacing.END),
getPadding(Spacing.BOTTOM),
UNSET);
// TODO: the Float.NaN should be replaced with the real line height see D3592781
uiViewOperationQueue.enqueueUpdateExtraData(getReactTag(), reactTextUpdate);
}
}
@ReactProp(name = "mostRecentEventCount")
public void setMostRecentEventCount(int mostRecentEventCount) {
mJsEventCount = mostRecentEventCount;
}
@ReactProp(name = ViewProps.NUMBER_OF_LINES, defaultInt = Integer.MAX_VALUE)
public void setNumberOfLines(int numberOfLines) {
mNumberOfLines = numberOfLines;
notifyChanged(true);
}
@ReactProp(name = PROP_TEXT)
public void setText(@Nullable String text) {
mText = text;
notifyChanged(true);
}
@Override
public void setPadding(int spacingType, float padding) {
super.setPadding(spacingType, padding);
mPaddingChanged = true;
dirty();
}
@Override
public boolean isPaddingChanged() {
return mPaddingChanged;
}
@Override
public void resetPaddingChanged() {
mPaddingChanged = false;
}
@Override
boolean shouldAllowEmptySpans() {
return true;
}
@Override
boolean isEditable() {
return true;
}
@Override
protected void performCollectText(SpannableStringBuilder builder) {
if (mText != null) {
builder.append(mText);
}
super.performCollectText(builder);
}
@Override
public boolean needsCustomLayoutForChildren() {
return false;
}
}

View File

@ -1,25 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import com.facebook.react.views.textinput.ReactTextInputManager;
public class RCTTextInputManager extends ReactTextInputManager {
/* package */ static final String REACT_CLASS = ReactTextInputManager.REACT_CLASS;
@Override
public RCTTextInput createShadowNodeInstance() {
return new RCTTextInput();
}
@Override
public Class<RCTTextInput> getShadowNodeClass() {
return RCTTextInput.class;
}
}

View File

@ -1,31 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
/**
* ViewManager that creates instances of RCTText.
*/
public final class RCTTextManager extends FlatViewManager {
/* package */ static final String REACT_CLASS = "RCTText";
@Override
public String getName() {
return REACT_CLASS;
}
@Override
public RCTText createShadowNodeInstance() {
return new RCTText();
}
@Override
public Class<RCTText> getShadowNodeClass() {
return RCTText.class;
}
}

View File

@ -1,185 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import javax.annotation.Nullable;
import android.graphics.Rect;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.uimanager.PixelUtil;
import com.facebook.react.uimanager.Spacing;
import com.facebook.react.uimanager.ReactStylesDiffMap;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.uimanager.annotations.ReactPropGroup;
/**
* Node for a react View.
*/
/* package */ final class RCTView extends FlatShadowNode {
private static final int[] SPACING_TYPES = {
Spacing.ALL, Spacing.LEFT, Spacing.RIGHT, Spacing.TOP, Spacing.BOTTOM,
};
private @Nullable DrawBorder mDrawBorder;
boolean mRemoveClippedSubviews;
boolean mHorizontal;
private @Nullable Rect mHitSlop;
@Override
/* package */ void handleUpdateProperties(ReactStylesDiffMap styles) {
mRemoveClippedSubviews = mRemoveClippedSubviews ||
(styles.hasKey(PROP_REMOVE_CLIPPED_SUBVIEWS) &&
styles.getBoolean(PROP_REMOVE_CLIPPED_SUBVIEWS, false));
if (mRemoveClippedSubviews) {
mHorizontal = mHorizontal ||
(styles.hasKey(PROP_HORIZONTAL) && styles.getBoolean(PROP_HORIZONTAL, false));
}
super.handleUpdateProperties(styles);
}
@Override
protected void collectState(
StateBuilder stateBuilder,
float left,
float top,
float right,
float bottom,
float clipLeft,
float clipTop,
float clipRight,
float clipBottom) {
super.collectState(
stateBuilder,
left,
top,
right,
bottom,
clipLeft,
clipTop,
clipRight,
clipBottom);
if (mDrawBorder != null) {
mDrawBorder = (DrawBorder) mDrawBorder.updateBoundsAndFreeze(
left,
top,
right,
bottom,
clipLeft,
clipTop,
clipRight,
clipBottom);
stateBuilder.addDrawCommand(mDrawBorder);
}
}
@Override
boolean doesDraw() {
return mDrawBorder != null || super.doesDraw();
}
@Override
public void setBackgroundColor(int backgroundColor) {
getMutableBorder().setBackgroundColor(backgroundColor);
}
@Override
public void setBorderWidths(int index, float borderWidth) {
super.setBorderWidths(index, borderWidth);
int type = SPACING_TYPES[index];
getMutableBorder().setBorderWidth(type, PixelUtil.toPixelFromDIP(borderWidth));
}
@ReactProp(name = "nativeBackgroundAndroid")
public void setHotspot(@Nullable ReadableMap bg) {
if (bg != null) {
forceMountToView();
}
}
@ReactPropGroup(names = {
"borderColor", "borderLeftColor", "borderRightColor", "borderTopColor", "borderBottomColor"
}, customType = "Color", defaultDouble = Double.NaN)
public void setBorderColor(int index, double color) {
int type = SPACING_TYPES[index];
if (Double.isNaN(color)) {
getMutableBorder().resetBorderColor(type);
} else {
getMutableBorder().setBorderColor(type, (int) color);
}
}
@ReactProp(name = "borderRadius")
public void setBorderRadius(float borderRadius) {
mClipRadius = borderRadius;
if (mClipToBounds && borderRadius > DrawView.MINIMUM_ROUNDED_CLIPPING_VALUE) {
// mount to a view if we are overflow: hidden and are clipping, so that we can do one
// clipPath to clip all the children of this node (both DrawCommands and Views).
forceMountToView();
}
getMutableBorder().setBorderRadius(PixelUtil.toPixelFromDIP(borderRadius));
}
@ReactProp(name = "borderStyle")
public void setBorderStyle(@Nullable String borderStyle) {
getMutableBorder().setBorderStyle(borderStyle);
}
@ReactProp(name = "hitSlop")
public void setHitSlop(@Nullable ReadableMap hitSlop) {
if (hitSlop == null) {
mHitSlop = null;
} else {
mHitSlop = new Rect(
(int) PixelUtil.toPixelFromDIP(hitSlop.getDouble("left")),
(int) PixelUtil.toPixelFromDIP(hitSlop.getDouble("top")),
(int) PixelUtil.toPixelFromDIP(hitSlop.getDouble("right")),
(int) PixelUtil.toPixelFromDIP(hitSlop.getDouble("bottom")));
}
}
@ReactProp(name = "pointerEvents")
public void setPointerEvents(@Nullable String pointerEventsStr) {
forceMountToView();
}
@Override
/* package */ void updateNodeRegion(
float left,
float top,
float right,
float bottom,
boolean isVirtual) {
if (!getNodeRegion().matches(left, top, right, bottom, isVirtual)) {
setNodeRegion(mHitSlop == null ?
new NodeRegion(left, top, right, bottom, getReactTag(), isVirtual) :
new HitSlopNodeRegion(mHitSlop, left, top, right, bottom, getReactTag(), isVirtual));
}
}
private DrawBorder getMutableBorder() {
if (mDrawBorder == null) {
mDrawBorder = new DrawBorder();
} else if (mDrawBorder.isFrozen()) {
mDrawBorder = (DrawBorder) mDrawBorder.mutableCopy();
}
invalidate();
return mDrawBorder;
}
@Override
public boolean clipsSubviews() {
return mRemoveClippedSubviews;
}
}

View File

@ -1,140 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import android.graphics.Rect;
import android.os.Build;
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.PixelUtil;
import com.facebook.react.uimanager.PointerEvents;
import com.facebook.react.uimanager.ReactClippingViewGroupHelper;
import com.facebook.react.uimanager.ViewProps;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.views.view.ReactDrawableHelper;
import javax.annotation.Nullable;
import java.util.Map;
/**
* ViewManager that creates instances of RCTView.
*/
public final class RCTViewManager extends FlatViewManager {
/* package */ static final String REACT_CLASS = ViewProps.VIEW_CLASS_NAME;
private static final int[] TMP_INT_ARRAY = new int[2];
private static final int CMD_HOTSPOT_UPDATE = 1;
private static final int CMD_SET_PRESSED = 2;
@Override
public String getName() {
return REACT_CLASS;
}
public Map<String, Integer> getCommandsMap() {
return MapBuilder.of("hotspotUpdate", CMD_HOTSPOT_UPDATE, "setPressed", CMD_SET_PRESSED);
}
@Override
public RCTView createShadowNodeInstance() {
return new RCTView();
}
@Override
public Class<RCTView> getShadowNodeClass() {
return RCTView.class;
}
@ReactProp(name = "nativeBackgroundAndroid")
public void setHotspot(FlatViewGroup view, @Nullable ReadableMap bg) {
view.setHotspot(bg == null ?
null : ReactDrawableHelper.createDrawableFromJSDescription(view.getContext(), bg));
}
@Override
public void receiveCommand(
FlatViewGroup view,
int commandId,
@Nullable ReadableArray args) {
switch (commandId) {
case CMD_HOTSPOT_UPDATE: {
if (args == null || args.size() != 2) {
throw new JSApplicationIllegalArgumentException(
"Illegal number of arguments for 'updateHotspot' command");
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
view.getLocationOnScreen(TMP_INT_ARRAY);
float x = PixelUtil.toPixelFromDIP(args.getDouble(0)) - TMP_INT_ARRAY[0];
float y = PixelUtil.toPixelFromDIP(args.getDouble(1)) - TMP_INT_ARRAY[1];
view.drawableHotspotChanged(x, y);
}
break;
}
case CMD_SET_PRESSED: {
if (args == null || args.size() != 1) {
throw new JSApplicationIllegalArgumentException(
"Illegal number of arguments for 'setPressed' command");
}
view.setPressed(args.getBoolean(0));
break;
}
}
}
@ReactProp(name = ViewProps.NEEDS_OFFSCREEN_ALPHA_COMPOSITING)
public void setNeedsOffscreenAlphaCompositing(
FlatViewGroup view,
boolean needsOffscreenAlphaCompositing) {
view.setNeedsOffscreenAlphaCompositing(needsOffscreenAlphaCompositing);
}
@ReactProp(name = "pointerEvents")
public void setPointerEvents(FlatViewGroup view, @Nullable String pointerEventsStr) {
view.setPointerEvents(parsePointerEvents(pointerEventsStr));
}
@ReactProp(name = ReactClippingViewGroupHelper.PROP_REMOVE_CLIPPED_SUBVIEWS)
public void setRemoveClippedSubviews(FlatViewGroup view, boolean removeClippedSubviews) {
view.setRemoveClippedSubviews(removeClippedSubviews);
}
private static PointerEvents parsePointerEvents(@Nullable String pointerEventsStr) {
if (pointerEventsStr != null) {
switch (pointerEventsStr) {
case "none":
return PointerEvents.NONE;
case "auto":
return PointerEvents.AUTO;
case "box-none":
return PointerEvents.BOX_NONE;
case "box-only":
return PointerEvents.BOX_ONLY;
}
}
// default or invalid
return PointerEvents.AUTO;
}
@ReactProp(name = "hitSlop")
public void setHitSlop(FlatViewGroup view, @Nullable ReadableMap hitSlop) {
if (hitSlop == null) {
view.setHitSlopRect(null);
} else {
view.setHitSlopRect(new Rect(
(int) PixelUtil.toPixelFromDIP(hitSlop.getDouble("left")),
(int) PixelUtil.toPixelFromDIP(hitSlop.getDouble("top")),
(int) PixelUtil.toPixelFromDIP(hitSlop.getDouble("right")),
(int) PixelUtil.toPixelFromDIP(hitSlop.getDouble("bottom"))
));
}
}
}

View File

@ -1,29 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import android.view.View;
import com.facebook.react.views.viewpager.ReactViewPager;
import com.facebook.react.views.viewpager.ReactViewPagerManager;
import java.util.List;
public class RCTViewPagerManager extends ReactViewPagerManager {
/* package */ static final String REACT_CLASS = ReactViewPagerManager.REACT_CLASS;
@Override
public void addViews(ReactViewPager parent, List<View> views) {
parent.setViews(views);
}
@Override
public void removeAllViews(ReactViewPager parent) {
parent.removeAllViewsFromAdapter();
}
}

View File

@ -1,307 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import android.graphics.Typeface;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.uimanager.PixelUtil;
import com.facebook.react.uimanager.ReactShadowNodeImpl;
import com.facebook.react.uimanager.ViewProps;
import com.facebook.react.uimanager.annotations.ReactProp;
import javax.annotation.Nullable;
/**
* 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";
private static final String PROP_SHADOW_OFFSET = "textShadowOffset";
private static final String PROP_SHADOW_RADIUS = "textShadowRadius";
private static final String PROP_SHADOW_COLOR = "textShadowColor";
private static final int DEFAULT_TEXT_SHADOW_COLOR = 0x55000000;
private FontStylingSpan mFontStylingSpan = FontStylingSpan.INSTANCE;
private ShadowStyleSpan mShadowStyleSpan = ShadowStyleSpan.INSTANCE;
@Override
public void addChildAt(ReactShadowNodeImpl child, int i) {
super.addChildAt(child, i);
notifyChanged(true);
}
@Override
protected void performCollectText(SpannableStringBuilder builder) {
for (int i = 0, childCount = getChildCount(); i < childCount; ++i) {
FlatTextShadowNode child = (FlatTextShadowNode) getChildAt(i);
child.collectText(builder);
}
}
@Override
protected void performApplySpans(SpannableStringBuilder builder, int begin, int end, boolean isEditable) {
mFontStylingSpan.freeze();
// All spans will automatically extend to the right of the text, but not the left - except
// for spans that start at the beginning of the text.
final int flag;
if (isEditable) {
flag = Spannable.SPAN_EXCLUSIVE_EXCLUSIVE;
} else {
flag = begin == 0 ?
Spannable.SPAN_INCLUSIVE_INCLUSIVE :
Spannable.SPAN_EXCLUSIVE_INCLUSIVE;
}
builder.setSpan(
mFontStylingSpan,
begin,
end,
flag);
if (mShadowStyleSpan.getColor() != 0 && mShadowStyleSpan.getRadius() != 0) {
mShadowStyleSpan.freeze();
builder.setSpan(
mShadowStyleSpan,
begin,
end,
flag);
}
for (int i = 0, childCount = getChildCount(); i < childCount; ++i) {
FlatTextShadowNode child = (FlatTextShadowNode) getChildAt(i);
child.applySpans(builder, isEditable);
}
}
@Override
protected void performCollectAttachDetachListeners(StateBuilder stateBuilder) {
for (int i = 0, childCount = getChildCount(); i < childCount; ++i) {
FlatTextShadowNode child = (FlatTextShadowNode) getChildAt(i);
child.performCollectAttachDetachListeners(stateBuilder);
}
}
@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 (mFontStylingSpan.getFontSize() != fontSize) {
getSpan().setFontSize(fontSize);
notifyChanged(true);
}
}
@ReactProp(name = ViewProps.COLOR, defaultDouble = Double.NaN)
public void setColor(double textColor) {
if (mFontStylingSpan.getTextColor() != textColor) {
getSpan().setTextColor(textColor);
notifyChanged(false);
}
}
@Override
public void setBackgroundColor(int backgroundColor) {
if (isVirtual()) {
// for nested Text elements, we want to apply background color to the text only
// e.g. Hello <style backgroundColor=red>World</style>, "World" will have red background color
if (mFontStylingSpan.getBackgroundColor() != backgroundColor) {
getSpan().setBackgroundColor(backgroundColor);
notifyChanged(false);
}
} else {
// for top-level Text element, background needs to be applied for the entire shadow node
//
// For example: <Text style={flex:1}>Hello World</Text>
// "Hello World" may only occupy e.g. 200 pixels, but the node may be measured at e.g. 500px.
// In this case, we want background to be 500px wide as well, and this is exactly what
// FlatShadowNode does.
super.setBackgroundColor(backgroundColor);
}
}
@ReactProp(name = ViewProps.FONT_FAMILY)
public void setFontFamily(@Nullable String fontFamily) {
if (!TextUtils.equals(mFontStylingSpan.getFontFamily(), fontFamily)) {
getSpan().setFontFamily(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 (mFontStylingSpan.getFontWeight() != fontWeight) {
getSpan().setFontWeight(fontWeight);
notifyChanged(true);
}
}
@ReactProp(name = ViewProps.TEXT_DECORATION_LINE)
public void setTextDecorationLine(@Nullable String textDecorationLineString) {
boolean isUnderlineTextDecorationSet = false;
boolean isLineThroughTextDecorationSet = false;
if (textDecorationLineString != null) {
for (String textDecorationLineSubString : textDecorationLineString.split(" ")) {
if ("underline".equals(textDecorationLineSubString)) {
isUnderlineTextDecorationSet = true;
} else if ("line-through".equals(textDecorationLineSubString)) {
isLineThroughTextDecorationSet = true;
}
}
}
if (isUnderlineTextDecorationSet != mFontStylingSpan.hasUnderline() ||
isLineThroughTextDecorationSet != mFontStylingSpan.hasStrikeThrough()) {
FontStylingSpan span = getSpan();
span.setHasUnderline(isUnderlineTextDecorationSet);
span.setHasStrikeThrough(isLineThroughTextDecorationSet);
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 (mFontStylingSpan.getFontStyle() != fontStyle) {
getSpan().setFontStyle(fontStyle);
notifyChanged(true);
}
}
@ReactProp(name = PROP_SHADOW_OFFSET)
public void setTextShadowOffset(@Nullable ReadableMap offsetMap) {
float dx = 0;
float dy = 0;
if (offsetMap != null) {
if (offsetMap.hasKey("width")) {
dx = PixelUtil.toPixelFromDIP(offsetMap.getDouble("width"));
}
if (offsetMap.hasKey("height")) {
dy = PixelUtil.toPixelFromDIP(offsetMap.getDouble("height"));
}
}
if (!mShadowStyleSpan.offsetMatches(dx, dy)) {
getShadowSpan().setOffset(dx, dy);
notifyChanged(false);
}
}
@ReactProp(name = PROP_SHADOW_RADIUS)
public void setTextShadowRadius(float textShadowRadius) {
textShadowRadius = PixelUtil.toPixelFromDIP(textShadowRadius);
if (mShadowStyleSpan.getRadius() != textShadowRadius) {
getShadowSpan().setRadius(textShadowRadius);
notifyChanged(false);
}
}
@ReactProp(name = PROP_SHADOW_COLOR, defaultInt = DEFAULT_TEXT_SHADOW_COLOR, customType = "Color")
public void setTextShadowColor(int textShadowColor) {
if (mShadowStyleSpan.getColor() != textShadowColor) {
getShadowSpan().setColor(textShadowColor);
notifyChanged(false);
}
}
/**
* Returns font size for this node.
* When called on RCTText, this value is never -1 (unset).
*/
protected final int getFontSize() {
return mFontStylingSpan.getFontSize();
}
/**
* Returns font style for this node.
*/
protected final int getFontStyle() {
int style = mFontStylingSpan.getFontStyle();
return style >= 0 ? style : Typeface.NORMAL;
}
protected int getDefaultFontSize() {
return -1;
}
/* package */ static int fontSizeFromSp(float sp) {
return (int) Math.ceil(PixelUtil.toPixelFromSP(sp));
}
protected final FontStylingSpan getSpan() {
if (mFontStylingSpan.isFrozen()) {
mFontStylingSpan = mFontStylingSpan.mutableCopy();
}
return mFontStylingSpan;
}
/**
* Returns a new SpannableStringBuilder that includes all the text and styling information to
* create the Layout.
*/
/* package */ final SpannableStringBuilder getText() {
SpannableStringBuilder sb = new SpannableStringBuilder();
collectText(sb);
applySpans(sb, isEditable());
return sb;
}
private final ShadowStyleSpan getShadowSpan() {
if (mShadowStyleSpan.isFrozen()) {
mShadowStyleSpan = mShadowStyleSpan.mutableCopy();
}
return mShadowStyleSpan;
}
/**
* 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;
}
}

View File

@ -1,31 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
/**
* ViewManager that creates instances of RCTVirtualText.
*/
public final class RCTVirtualTextManager extends VirtualViewManager<RCTVirtualText> {
/* package */ static final String REACT_CLASS = "RCTVirtualText";
@Override
public String getName() {
return REACT_CLASS;
}
@Override
public RCTVirtualText createShadowNodeInstance() {
return new RCTVirtualText();
}
@Override
public Class<RCTVirtualText> getShadowNodeClass() {
return RCTVirtualText.class;
}
}

View File

@ -1,49 +0,0 @@
# Nodes
Nodes is an experimental, alternate version of
[UIImplementation](https://github.com/facebook/react-native/blob/master/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java) for ReactNative on Android. It has two main advantages over the existing `UIImplementation`:
1. Support for `overflow:visible` on Android.
2. More efficient generation of view hierarchies.
The intention is to ultimately replace the existing `UIImplementation` on
Android with Nodes (after all the issues are ironed out).
## How to test
In a subclass of `ReactNativeHost`, add this:
```java
@Override
protected UIImplementationProvider getUIImplementationProvider() {
return new FlatUIImplementationProvider();
}
```
## How it Works
The existing
[UIImplementation](https://github.com/facebook/react-native/blob/master/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIImplementation.java) maps all non-layout tags to `View`s (resulting in an almost 1:1 mapping of tags
to Views, with the exception of some optimizations for layout only tags that
don't draw content). Nodes, on the other hand, maps react tags to a set of
`DrawCommand`s. In other words, an `<image>` tag will often be mapped to a
`Drawable` instead of an `ImageView` and a `<text>` tag will be mapped to a
`Layout` instead of a `TextView`. This helps flatten the resulting `View`
hierarchy.
There are situations where `DrawCommand`s are promoted to `View`s:
1. Existing Android components that are wrapped by React Native (for example,
`ViewPager`, `ScrollView`, etc).
2. When using a `View` is more optimal (for example, `opacity`, to avoid
unnecessary invalidations).
3. To facilitate the implementation of certain features (accessibility,
transforms, etc).
This means that existing custom `ViewManager`s should continue to work as they
did with the existing `UIImplementation`.
## Limitations and Known Issues
- `LayoutAnimation`s are not yet supported
- `zIndex` is not yet supported

View File

@ -1,72 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import android.text.TextPaint;
import android.text.style.CharacterStyle;
/* package */ final class ShadowStyleSpan extends CharacterStyle {
/* package */ static final ShadowStyleSpan INSTANCE = new ShadowStyleSpan(0, 0, 0, 0, true);
private float mDx;
private float mDy;
private float mRadius;
private int mColor;
private boolean mFrozen;
private ShadowStyleSpan(float dx, float dy, float radius, int color, boolean frozen) {
mDx = dx;
mDy = dy;
mRadius = radius;
mColor = color;
mFrozen = frozen;
}
public boolean offsetMatches(float dx, float dy) {
return mDx == dx && mDy == dy;
}
public void setOffset(float dx, float dy) {
mDx = dx;
mDy = dy;
}
public float getRadius() {
return mRadius;
}
public void setRadius(float radius) {
mRadius = radius;
}
public int getColor() {
return mColor;
}
public void setColor(int color) {
mColor = color;
}
/* package */ ShadowStyleSpan mutableCopy() {
return new ShadowStyleSpan(mDx, mDy, mRadius, mColor, false);
}
/* package */ boolean isFrozen() {
return mFrozen;
}
/* package */ void freeze() {
mFrozen = true;
}
@Override
public void updateDrawState(TextPaint textPaint) {
textPaint.setShadowLayer(mRadius, mDx, mDy, mColor);
}
}

View File

@ -1,729 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import javax.annotation.Nullable;
import java.util.ArrayList;
import android.util.SparseIntArray;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.uimanager.OnLayoutEvent;
import com.facebook.react.uimanager.Spacing;
import com.facebook.react.uimanager.ReactShadowNode;
import com.facebook.react.uimanager.ReactStylesDiffMap;
import com.facebook.react.uimanager.UIViewOperationQueue;
import com.facebook.react.uimanager.events.EventDispatcher;
/**
* 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 walks the shadow node tree
* and collects information into an operation queue that is run on the UI thread and applied to the
* non-shadow hierarchy of Views that Android can finally display.
*/
/* package */ final class StateBuilder {
/* package */ static final float[] EMPTY_FLOAT_ARRAY = new float[0];
/* package */ static final SparseIntArray EMPTY_SPARSE_INT = new SparseIntArray();
private static final boolean SKIP_UP_TO_DATE_NODES = true;
// Optimization to avoid re-allocating zero length arrays.
private static final int[] EMPTY_INT_ARRAY = new int[0];
private final FlatUIViewOperationQueue mOperationsQueue;
private final ElementsList<DrawCommand> mDrawCommands =
new ElementsList<>(DrawCommand.EMPTY_ARRAY);
private final ElementsList<AttachDetachListener> mAttachDetachListeners =
new ElementsList<>(AttachDetachListener.EMPTY_ARRAY);
private final ElementsList<NodeRegion> mNodeRegions =
new ElementsList<>(NodeRegion.EMPTY_ARRAY);
private final ElementsList<FlatShadowNode> mNativeChildren =
new ElementsList<>(FlatShadowNode.EMPTY_ARRAY);
private final ArrayList<FlatShadowNode> mViewsToDetachAllChildrenFrom = new ArrayList<>();
private final ArrayList<FlatShadowNode> mViewsToDetach = new ArrayList<>();
private final ArrayList<Integer> mViewsToDrop = new ArrayList<>();
private final ArrayList<Integer> mParentsForViewsToDrop = new ArrayList<>();
private final ArrayList<OnLayoutEvent> mOnLayoutEvents = new ArrayList<>();
private final ArrayList<UIViewOperationQueue.UIOperation> mUpdateViewBoundsOperations =
new ArrayList<>();
private final ArrayList<UIViewOperationQueue.UIOperation> mViewManagerCommands =
new ArrayList<>();
private @Nullable FlatUIViewOperationQueue.DetachAllChildrenFromViews mDetachAllChildrenFromViews;
/* package */ StateBuilder(FlatUIViewOperationQueue operationsQueue) {
mOperationsQueue = operationsQueue;
}
/* package */ FlatUIViewOperationQueue getOperationsQueue() {
return mOperationsQueue;
}
/**
* Given a root of the laid-out shadow node hierarchy, walks the tree and generates arrays from
* element lists that are mounted in the UI thread to FlatViewGroups to handle drawing, touch,
* and other logic.
*/
/* package */ void applyUpdates(FlatShadowNode node) {
float width = node.getLayoutWidth();
float height = node.getLayoutHeight();
float left = node.getLayoutX();
float top = node.getLayoutY();
float right = left + width;
float bottom = top + height;
collectStateForMountableNode(
node,
left,
top,
right,
bottom,
Float.NEGATIVE_INFINITY,
Float.NEGATIVE_INFINITY,
Float.POSITIVE_INFINITY,
Float.POSITIVE_INFINITY);
updateViewBounds(node, left, top, right, bottom);
}
/**
* Run after the shadow node hierarchy is updated. Detaches all children from Views that are
* changing their native children, updates views, and dispatches commands before discarding any
* dropped views.
*
* @param eventDispatcher Dispatcher for onLayout events.
*/
void afterUpdateViewHierarchy(EventDispatcher eventDispatcher) {
if (mDetachAllChildrenFromViews != null) {
int[] viewsToDetachAllChildrenFrom = collectViewTags(mViewsToDetachAllChildrenFrom);
mViewsToDetachAllChildrenFrom.clear();
mDetachAllChildrenFromViews.setViewsToDetachAllChildrenFrom(viewsToDetachAllChildrenFrom);
mDetachAllChildrenFromViews = null;
}
for (int i = 0, size = mUpdateViewBoundsOperations.size(); i != size; ++i) {
mOperationsQueue.enqueueFlatUIOperation(mUpdateViewBoundsOperations.get(i));
}
mUpdateViewBoundsOperations.clear();
// Process view manager commands after bounds operations, so that any UI operations have already
// happened before we actually dispatch the view manager command. This prevents things like
// commands going to empty parents and views not yet being created.
for (int i = 0, size = mViewManagerCommands.size(); i != size; i++) {
mOperationsQueue.enqueueFlatUIOperation(mViewManagerCommands.get(i));
}
mViewManagerCommands.clear();
// This could be more efficient if EventDispatcher had a batch mode
// to avoid multiple synchronized calls.
for (int i = 0, size = mOnLayoutEvents.size(); i != size; ++i) {
eventDispatcher.dispatchEvent(mOnLayoutEvents.get(i));
}
mOnLayoutEvents.clear();
if (mViewsToDrop.size() > 0) {
mOperationsQueue.enqueueDropViews(mViewsToDrop, mParentsForViewsToDrop);
mViewsToDrop.clear();
mParentsForViewsToDrop.clear();
}
mOperationsQueue.enqueueProcessLayoutRequests();
}
/* package */ void removeRootView(int rootViewTag) {
// Note root view tags with a negative value.
mViewsToDrop.add(-rootViewTag);
mParentsForViewsToDrop.add(-1);
}
/**
* Adds a draw command to the element list for the current scope. Allows collectState within the
* shadow node to add commands.
*
* @param drawCommand The draw command to add.
*/
/* package */ void addDrawCommand(AbstractDrawCommand drawCommand) {
mDrawCommands.add(drawCommand);
}
/**
* Adds a listener to the element list for the current scope. Allows collectState within the
* shadow node to add listeners.
*
* @param listener The listener to add
*/
/* package */ void addAttachDetachListener(AttachDetachListener listener) {
mAttachDetachListeners.add(listener);
}
/**
* Adds a command for a view manager to the queue. We have to delay adding it to the operations
* queue until we have added our view moves, creations and updates.
*
* @param reactTag The react tag of the command target.
* @param commandId ID of the command.
* @param commandArgs Arguments for the command.
*/
/* package */ void enqueueViewManagerCommand(
int reactTag,
int commandId,
ReadableArray commandArgs) {
mViewManagerCommands.add(
mOperationsQueue.createViewManagerCommand(reactTag, commandId, commandArgs));
}
/**
* Create a backing view for a node, or update the backing view if it has already been created.
*
* @param node The node to create the backing view for.
* @param styles Styles for the view.
*/
/* package */ void enqueueCreateOrUpdateView(
FlatShadowNode node,
@Nullable ReactStylesDiffMap styles) {
if (node.isBackingViewCreated()) {
// If the View is already created, make sure to propagate the new styles.
mOperationsQueue.enqueueUpdateProperties(
node.getReactTag(),
node.getViewClass(),
styles);
} else {
mOperationsQueue.enqueueCreateView(
node.getThemedContext(),
node.getReactTag(),
node.getViewClass(),
styles);
node.signalBackingViewIsCreated();
}
}
/**
* Create a backing view for a node if not already created.
*
* @param node The node to create the backing view for.
*/
/* package */ void ensureBackingViewIsCreated(FlatShadowNode node) {
if (node.isBackingViewCreated()) {
return;
}
int tag = node.getReactTag();
mOperationsQueue.enqueueCreateView(node.getThemedContext(), tag, node.getViewClass(), null);
node.signalBackingViewIsCreated();
}
/**
* Enqueue dropping of the view for a node that has a backing view. Used in conjunction with
* remove the node from the shadow hierarchy.
*
* @param node The node to drop the backing view for.
*/
/* package */ void dropView(FlatShadowNode node, int parentReactTag) {
mViewsToDrop.add(node.getReactTag());
mParentsForViewsToDrop.add(parentReactTag);
}
/**
* Adds a node region to the element list for the current scope. Allows collectState to add
* regions.
*
* @param node The node to add a region for.
* @param left Bound of the region.
* @param top Bound of the region.
* @param right Bound of the region.
* @param bottom Bound of the region.
* @param isVirtual True if the region does not map to a native view. Used to determine touch
* targets.
*/
private void addNodeRegion(
FlatShadowNode node,
float left,
float top,
float right,
float bottom,
boolean isVirtual) {
if (left == right || top == bottom) {
// no point in adding an empty NodeRegion
return;
}
node.updateNodeRegion(left, top, right, bottom, isVirtual);
if (node.doesDraw()) {
mNodeRegions.add(node.getNodeRegion());
}
}
/**
* Adds a native child to the element list for the current scope. Allows collectState to add
* native children.
*
* @param nativeChild The view-backed native child to add.
*/
private void addNativeChild(FlatShadowNode nativeChild) {
mNativeChildren.add(nativeChild);
}
/**
* Updates boundaries of a View that a give nodes maps to.
*/
private void updateViewBounds(
FlatShadowNode node,
float left,
float top,
float right,
float bottom) {
int viewLeft = Math.round(left);
int viewTop = Math.round(top);
int viewRight = Math.round(right);
int viewBottom = Math.round(bottom);
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);
int tag = node.getReactTag();
mUpdateViewBoundsOperations.add(
mOperationsQueue.createUpdateViewBounds(tag, viewLeft, viewTop, viewRight, viewBottom));
}
/**
* Collects state (Draw commands, listeners, regions, native children) for a given node that will
* mount to a View. Returns true if this node or any of its descendants that mount to View
* generated any updates.
*/
private boolean collectStateForMountableNode(
FlatShadowNode node,
float left,
float top,
float right,
float bottom,
float clipLeft,
float clipTop,
float clipRight,
float clipBottom) {
boolean hasUpdates = node.hasNewLayout();
boolean expectingUpdate = hasUpdates || node.isUpdated() || node.hasUnseenUpdates() ||
node.clipBoundsChanged(clipLeft, clipTop, clipRight, clipBottom);
if (SKIP_UP_TO_DATE_NODES && !expectingUpdate) {
return false;
}
node.setClipBounds(clipLeft, clipTop, clipRight, clipBottom);
mDrawCommands.start(node.getDrawCommands());
mAttachDetachListeners.start(node.getAttachDetachListeners());
mNodeRegions.start(node.getNodeRegions());
mNativeChildren.start(node.getNativeChildren());
boolean isAndroidView = false;
boolean needsCustomLayoutForChildren = false;
if (node instanceof AndroidView) {
AndroidView androidView = (AndroidView) node;
updateViewPadding(androidView, node.getReactTag());
isAndroidView = true;
needsCustomLayoutForChildren = androidView.needsCustomLayoutForChildren();
// AndroidView might scroll (e.g. ScrollView) so we need to reset clip bounds here
// Otherwise, we might scroll clipped content. If AndroidView doesn't scroll, this is still
// harmless, because AndroidView will do its own clipping anyway.
clipLeft = Float.NEGATIVE_INFINITY;
clipTop = Float.NEGATIVE_INFINITY;
clipRight = Float.POSITIVE_INFINITY;
clipBottom = Float.POSITIVE_INFINITY;
}
if (!isAndroidView && node.isVirtualAnchor()) {
// If RCTText is mounted to View, virtual children will not receive any touch events
// because they don't get added to nodeRegions, so nodeRegions will be empty and
// FlatViewGroup.reactTagForTouch() will always return RCTText's id. To fix the issue,
// manually add nodeRegion so it will have exactly one NodeRegion, and virtual nodes will
// be able to receive touch events.
addNodeRegion(node, left, top, right, bottom, true);
}
boolean descendantUpdated = collectStateRecursively(
node,
left,
top,
right,
bottom,
clipLeft,
clipTop,
clipRight,
clipBottom,
isAndroidView,
needsCustomLayoutForChildren);
boolean shouldUpdateMountState = false;
final DrawCommand[] drawCommands = mDrawCommands.finish();
if (drawCommands != null) {
shouldUpdateMountState = true;
node.setDrawCommands(drawCommands);
}
final AttachDetachListener[] listeners = mAttachDetachListeners.finish();
if (listeners != null) {
shouldUpdateMountState = true;
node.setAttachDetachListeners(listeners);
}
final NodeRegion[] nodeRegions = mNodeRegions.finish();
if (nodeRegions != null) {
shouldUpdateMountState = true;
node.setNodeRegions(nodeRegions);
} else if (descendantUpdated) {
// one of the descendant's value for overflows container may have changed, so
// we still need to update ours.
node.updateOverflowsContainer();
}
// We need to finish the native children so that we can process clipping FlatViewGroup.
final FlatShadowNode[] nativeChildren = mNativeChildren.finish();
if (shouldUpdateMountState) {
if (node.clipsSubviews()) {
// Node is a clipping FlatViewGroup, so lets do some calculations off the UI thread.
// DrawCommandManager has a better explanation of the data incoming from these calculations,
// and is where they are actually used.
float[] commandMaxBottom = EMPTY_FLOAT_ARRAY;
float[] commandMinTop = EMPTY_FLOAT_ARRAY;
SparseIntArray drawViewIndexMap = EMPTY_SPARSE_INT;
if (drawCommands != null) {
drawViewIndexMap = new SparseIntArray();
commandMaxBottom = new float[drawCommands.length];
commandMinTop = new float[drawCommands.length];
if (node.isHorizontal()) {
HorizontalDrawCommandManager
.fillMaxMinArrays(drawCommands, commandMaxBottom, commandMinTop, drawViewIndexMap);
} else {
VerticalDrawCommandManager
.fillMaxMinArrays(drawCommands, commandMaxBottom, commandMinTop, drawViewIndexMap);
}
}
float[] regionMaxBottom = EMPTY_FLOAT_ARRAY;
float[] regionMinTop = EMPTY_FLOAT_ARRAY;
if (nodeRegions != null) {
regionMaxBottom = new float[nodeRegions.length];
regionMinTop = new float[nodeRegions.length];
if (node.isHorizontal()) {
HorizontalDrawCommandManager
.fillMaxMinArrays(nodeRegions, regionMaxBottom, regionMinTop);
} else {
VerticalDrawCommandManager
.fillMaxMinArrays(nodeRegions, regionMaxBottom, regionMinTop);
}
}
boolean willMountViews = nativeChildren != null;
mOperationsQueue.enqueueUpdateClippingMountState(
node.getReactTag(),
drawCommands,
drawViewIndexMap,
commandMaxBottom,
commandMinTop,
listeners,
nodeRegions,
regionMaxBottom,
regionMinTop,
willMountViews);
} else {
mOperationsQueue.enqueueUpdateMountState(
node.getReactTag(),
drawCommands,
listeners,
nodeRegions);
}
}
if (node.hasUnseenUpdates()) {
node.onCollectExtraUpdates(mOperationsQueue);
node.markUpdateSeen();
}
if (nativeChildren != null) {
updateNativeChildren(node, node.getNativeChildren(), nativeChildren);
}
boolean updated = shouldUpdateMountState || nativeChildren != null || descendantUpdated;
if (!expectingUpdate && updated) {
throw new RuntimeException("Node " + node.getReactTag() + " updated unexpectedly.");
}
return updated;
}
/**
* Handles updating the children of a node when they change. Updates the shadow node and
* enqueues state updates that will eventually be run on the UI thread.
*
* @param node The node to update native children for.
* @param oldNativeChildren The previously mounted native children.
* @param newNativeChildren The newly mounted native children.
*/
private void updateNativeChildren(
FlatShadowNode node,
FlatShadowNode[] oldNativeChildren,
FlatShadowNode[] newNativeChildren) {
node.setNativeChildren(newNativeChildren);
if (mDetachAllChildrenFromViews == null) {
mDetachAllChildrenFromViews = mOperationsQueue.enqueueDetachAllChildrenFromViews();
}
if (oldNativeChildren.length != 0) {
mViewsToDetachAllChildrenFrom.add(node);
}
int tag = node.getReactTag();
int numViewsToAdd = newNativeChildren.length;
final int[] viewsToAdd;
if (numViewsToAdd == 0) {
viewsToAdd = EMPTY_INT_ARRAY;
} else {
viewsToAdd = new int[numViewsToAdd];
int i = 0;
for (FlatShadowNode child : newNativeChildren) {
if (child.getNativeParentTag() == tag) {
viewsToAdd[i] = -child.getReactTag();
} else {
viewsToAdd[i] = child.getReactTag();
}
// all views we add are first start detached
child.setNativeParentTag(-1);
++i;
}
}
// Populate an array of views to detach.
// These views still have their native parent set as opposed to being reset to -1
for (FlatShadowNode child : oldNativeChildren) {
if (child.getNativeParentTag() == tag) {
// View is attached to old parent and needs to be removed.
mViewsToDetach.add(child);
child.setNativeParentTag(-1);
}
}
final int[] viewsToDetach = collectViewTags(mViewsToDetach);
mViewsToDetach.clear();
// restore correct parent tag
for (FlatShadowNode child : newNativeChildren) {
child.setNativeParentTag(tag);
}
mOperationsQueue.enqueueUpdateViewGroup(tag, viewsToAdd, viewsToDetach);
}
/**
* Recursively walks node tree from a given node and collects draw commands, listeners, node
* regions and native children. Calls collect state on the node, then processNodeAndCollectState
* for the recursion.
*/
private boolean collectStateRecursively(
FlatShadowNode node,
float left,
float top,
float right,
float bottom,
float clipLeft,
float clipTop,
float clipRight,
float clipBottom,
boolean isAndroidView,
boolean needsCustomLayoutForChildren) {
if (node.hasNewLayout()) {
node.markLayoutSeen();
}
float roundedLeft = roundToPixel(left);
float roundedTop = roundToPixel(top);
float roundedRight = roundToPixel(right);
float roundedBottom = roundToPixel(bottom);
// notify JS about layout event if requested
if (node.shouldNotifyOnLayout()) {
OnLayoutEvent layoutEvent = node.obtainLayoutEvent(
Math.round(node.getLayoutX()),
Math.round(node.getLayoutY()),
(int) (roundedRight - roundedLeft),
(int) (roundedBottom - roundedTop));
if (layoutEvent != null) {
mOnLayoutEvents.add(layoutEvent);
}
}
if (node.clipToBounds()) {
clipLeft = Math.max(left, clipLeft);
clipTop = Math.max(top, clipTop);
clipRight = Math.min(right, clipRight);
clipBottom = Math.min(bottom, clipBottom);
}
node.collectState(
this,
roundedLeft,
roundedTop,
roundedRight,
roundedBottom,
roundToPixel(clipLeft),
roundToPixel(clipTop),
roundToPixel(clipRight),
clipBottom);
boolean updated = false;
for (int i = 0, childCount = node.getChildCount(); i != childCount; ++i) {
ReactShadowNode child = node.getChildAt(i);
if (child.isVirtual()) {
continue;
}
updated |= processNodeAndCollectState(
(FlatShadowNode) child,
left,
top,
clipLeft,
clipTop,
clipRight,
clipBottom,
isAndroidView,
needsCustomLayoutForChildren);
}
node.resetUpdated();
return updated;
}
/**
* Collects state and enqueues View boundary updates for a given node tree. Returns true if
* this node or any of its descendants that mount to View generated any updates.
*/
private boolean processNodeAndCollectState(
FlatShadowNode node,
float parentLeft,
float parentTop,
float parentClipLeft,
float parentClipTop,
float parentClipRight,
float parentClipBottom,
boolean parentIsAndroidView,
boolean needsCustomLayout) {
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;
boolean mountsToView = node.mountsToView();
final boolean updated;
if (!parentIsAndroidView) {
addNodeRegion(node, left, top, right, bottom, !mountsToView);
}
if (mountsToView) {
ensureBackingViewIsCreated(node);
addNativeChild(node);
updated = collectStateForMountableNode(
node,
0, // left - left
0, // top - top
right - left,
bottom - top,
parentClipLeft - left,
parentClipTop - top,
parentClipRight - left,
parentClipBottom - top);
if (!parentIsAndroidView) {
mDrawCommands.add(node.collectDrawView(
left,
top,
right,
bottom,
parentClipLeft,
parentClipTop,
parentClipRight,
parentClipBottom));
}
if (!needsCustomLayout) {
updateViewBounds(node, left, top, right, bottom);
}
} else {
updated = collectStateRecursively(
node,
left,
top,
right,
bottom,
parentClipLeft,
parentClipTop,
parentClipRight,
parentClipBottom,
false,
false);
}
return updated;
}
private void updateViewPadding(AndroidView androidView, int reactTag) {
if (androidView.isPaddingChanged()) {
mOperationsQueue.enqueueSetPadding(
reactTag,
Math.round(androidView.getPadding(Spacing.LEFT)),
Math.round(androidView.getPadding(Spacing.TOP)),
Math.round(androidView.getPadding(Spacing.RIGHT)),
Math.round(androidView.getPadding(Spacing.BOTTOM)));
androidView.resetPaddingChanged();
}
}
private static int[] collectViewTags(ArrayList<FlatShadowNode> views) {
int numViews = views.size();
if (numViews == 0) {
return EMPTY_INT_ARRAY;
}
int[] viewTags = new int[numViews];
for (int i = 0; i < numViews; ++i) {
viewTags[i] = views.get(i).getReactTag();
}
return viewTags;
}
/**
* This is what Math.round() does, except it returns float.
*/
private static float roundToPixel(float pos) {
return (float) Math.floor(pos + 0.5f);
}
}

View File

@ -1,84 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import javax.annotation.Nullable;
import android.text.Layout;
import android.text.Spanned;
/* package */ final class TextNodeRegion extends NodeRegion {
private @Nullable Layout mLayout;
/* package */ TextNodeRegion(
float left,
float top,
float right,
float bottom,
int tag,
boolean isVirtual,
@Nullable Layout layout) {
super(left, top, right, bottom, tag, isVirtual);
mLayout = layout;
}
public void setLayout(Layout layout) {
mLayout = layout;
}
/* package */ @Nullable Layout getLayout() {
return mLayout;
}
/* package */ int getReactTag(float touchX, float touchY) {
if (mLayout != null) {
CharSequence text = mLayout.getText();
if (text instanceof Spanned) {
int y = Math.round(touchY - getTop());
if (y >= mLayout.getLineTop(0) && y < mLayout.getLineBottom(mLayout.getLineCount() - 1)) {
float x = Math.round(touchX - getLeft());
int line = mLayout.getLineForVertical(y);
if (mLayout.getLineLeft(line) <= x && x <= mLayout.getLineRight(line)) {
int off = mLayout.getOffsetForHorizontal(line, x);
Spanned spanned = (Spanned) text;
RCTRawText[] link = spanned.getSpans(off, off, RCTRawText.class);
if (link.length != 0) {
return link[0].getReactTag();
}
}
}
}
}
return super.getReactTag(touchX, touchY);
}
@Override
boolean matchesTag(int tag) {
if (super.matchesTag(tag)) {
return true;
}
if (mLayout != null) {
CharSequence text = mLayout.getText();
if (text instanceof Spanned) {
Spanned spannedText = (Spanned) text;
RCTRawText[] spans = spannedText.getSpans(0, text.length(), RCTRawText.class);
for (RCTRawText span : spans) {
if (span.getReactTag() == tag) {
return true;
}
}
}
}
return false;
}
}

View File

@ -1,115 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import javax.annotation.Nullable;
import java.util.HashMap;
import android.content.res.AssetManager;
import android.graphics.Typeface;
import com.facebook.infer.annotation.Assertions;
/**
* TypefaceCache provides methods to resolve typeface from font family, or existing typeface
* with a different style.
*/
/* package */ final class TypefaceCache {
private static final int MAX_STYLES = 4; // NORMAL = 0, BOLD = 1, ITALIC = 2, BOLD_ITALIC = 3
private static final HashMap<String, Typeface[]> FONTFAMILY_CACHE = new HashMap<>();
private static final HashMap<Typeface, Typeface[]> TYPEFACE_CACHE = new HashMap<>();
private static final String[] EXTENSIONS = {
"",
"_bold",
"_italic",
"_bold_italic"};
private static final String[] FILE_EXTENSIONS = {".ttf", ".otf"};
private static final String FONTS_ASSET_PATH = "fonts/";
@Nullable private static AssetManager sAssetManager = null;
public static void setAssetManager(AssetManager assetManager) {
sAssetManager = assetManager;
}
/**
* Returns a Typeface for a given a FontFamily and style.
*/
public static Typeface getTypeface(String fontFamily, int style) {
Typeface[] cache = FONTFAMILY_CACHE.get(fontFamily);
if (cache == null) {
// cache is empty, create one.
cache = new Typeface[MAX_STYLES];
FONTFAMILY_CACHE.put(fontFamily, cache);
} else if (cache[style] != null) {
// return cached value.
return cache[style];
}
Typeface typeface = createTypeface(fontFamily, style);
cache[style] = typeface;
TYPEFACE_CACHE.put(typeface, cache);
return typeface;
}
private static Typeface createTypeface(String fontFamilyName, int style) {
String extension = EXTENSIONS[style];
StringBuilder fileNameBuffer = new StringBuilder(32)
.append(FONTS_ASSET_PATH)
.append(fontFamilyName)
.append(extension);
int length = fileNameBuffer.length();
for (String fileExtension : FILE_EXTENSIONS) {
String fileName = fileNameBuffer.append(fileExtension).toString();
try {
return Typeface.createFromAsset(sAssetManager, fileName);
} catch (RuntimeException e) {
// unfortunately Typeface.createFromAsset throws an exception instead of returning null
// if the typeface doesn't exist
fileNameBuffer.setLength(length);
}
}
return Assertions.assumeNotNull(Typeface.create(fontFamilyName, style));
}
/**
* Returns a derivative of a given Typeface with a different style.
*/
public static Typeface getTypeface(Typeface typeface, int style) {
if (typeface == null) {
return Typeface.defaultFromStyle(style);
}
Typeface[] cache = TYPEFACE_CACHE.get(typeface);
if (cache == null) {
// This should not happen because all Typefaces are coming from TypefaceCache,
// and thus should be registered in TYPEFACE_CACHE.
// If we get here, it's a bug and one of the 2 scenarios happened:
// a) TypefaceCache created a Typeface and didn't put it into TYPEFACE_CACHE.
// b) someone else created a Typeface bypassing TypefaceCache so it's not registered here.
//
// If it's not registered, we can just register it manually for consistency, and so that
// next time someone requests a un unknown Typeface, it's already cached and we don't create
// extra copies.
cache = new Typeface[MAX_STYLES];
cache[typeface.getStyle()] = typeface;
} else if (cache[style] != null) {
// return cached value.
return cache[style];
}
typeface = Typeface.create(typeface, style);
cache[style] = typeface;
TYPEFACE_CACHE.put(typeface, cache);
return typeface;
}
}

View File

@ -1,124 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import java.util.Arrays;
import android.util.SparseIntArray;
/**
* {@link DrawCommandManager} with vertical clipping (The view scrolls up and down).
*/
/* package */ final class VerticalDrawCommandManager extends ClippingDrawCommandManager {
/* package */ VerticalDrawCommandManager(
FlatViewGroup flatViewGroup,
DrawCommand[] drawCommands) {
super(flatViewGroup, drawCommands);
}
@Override
int commandStartIndex() {
int start = Arrays.binarySearch(mCommandMaxBottom, mClippingRect.top);
// We don't care whether we matched or not, but positive indices are helpful. The binary search
// returns ~index in the case that it isn't a match, so reverse that here.
return start < 0 ? ~start : start;
}
@Override
int commandStopIndex(int start) {
int stop = Arrays.binarySearch(
mCommandMinTop,
start,
mCommandMinTop.length,
mClippingRect.bottom);
// We don't care whether we matched or not, but positive indices are helpful. The binary search
// returns ~index in the case that it isn't a match, so reverse that here.
return stop < 0 ? ~stop : stop;
}
@Override
int regionStopIndex(float touchX, float touchY) {
int stop = Arrays.binarySearch(mRegionMinTop, touchY + 0.0001f);
// We don't care whether we matched or not, but positive indices are helpful. The binary search
// returns ~index in the case that it isn't a match, so reverse that here.
return stop < 0 ? ~stop : stop;
}
@Override
boolean regionAboveTouch(int index, float touchX, float touchY) {
return mRegionMaxBottom[index] < touchY;
}
/**
* Populates the max and min arrays for a given set of node regions.
*
* This should never be called from the UI thread, as the reason it exists is to do work off the
* UI thread.
*
* @param regions The regions that will eventually be mounted.
* @param maxBottom At each index i, the maximum bottom value of all regions at or below i.
* @param minTop At each index i, the minimum top value of all regions at or below i.
*/
public static void fillMaxMinArrays(NodeRegion[] regions, float[] maxBottom, float[] minTop) {
float last = 0;
for (int i = 0; i < regions.length; i++) {
last = Math.max(last, regions[i].getTouchableBottom());
maxBottom[i] = last;
}
for (int i = regions.length - 1; i >= 0; i--) {
last = Math.min(last, regions[i].getTouchableTop());
minTop[i] = last;
}
}
/**
* Populates the max and min arrays for a given set of draw commands. Also populates a mapping of
* react tags to their index position in the command array.
*
* This should never be called from the UI thread, as the reason it exists is to do work off the
* UI thread.
*
* @param commands The draw commands that will eventually be mounted.
* @param maxBottom At each index i, the maximum bottom value of all draw commands at or below i.
* @param minTop At each index i, the minimum top value of all draw commands at or below i.
* @param drawViewIndexMap Mapping of ids to index position within the draw command array.
*/
public static void fillMaxMinArrays(
DrawCommand[] commands,
float[] maxBottom,
float[] minTop,
SparseIntArray drawViewIndexMap) {
float last = 0;
// Loop through the DrawCommands, keeping track of the maximum we've seen if we only iterated
// through items up to this position.
for (int i = 0; i < commands.length; i++) {
if (commands[i] instanceof DrawView) {
DrawView drawView = (DrawView) commands[i];
// These will generally be roughly sorted by id, so try to insert at the end if possible.
drawViewIndexMap.append(drawView.reactTag, i);
last = Math.max(last, drawView.mLogicalBottom);
} else {
last = Math.max(last, commands[i].getBottom());
}
maxBottom[i] = last;
}
// Intentionally leave last as it was, since it's at the maximum bottom position we've seen so
// far, we can use it again.
// Loop through backwards, keeping track of the minimum we've seen at this position.
for (int i = commands.length - 1; i >= 0; i--) {
if (commands[i] instanceof DrawView) {
last = Math.min(last, ((DrawView) commands[i]).mLogicalTop);
} else {
last = Math.min(last, commands[i].getTop());
}
minTop[i] = last;
}
}
}

View File

@ -1,14 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.flat;
import android.view.View;
public interface ViewResolver {
public View getView(int tag);
}

View File

@ -1,27 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
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<C extends FlatShadowNode> extends ViewManager<View, C> {
@Override
protected View createViewInstance(ThemedReactContext reactContext) {
throw new RuntimeException(getName() + " doesn't map to a View");
}
@Override
public void updateExtraData(View root, Object extraData) {
}
}

View File

@ -19,7 +19,6 @@ rn_android_library(
react_native_target("java/com/facebook/react/bridge:bridge"),
react_native_target("java/com/facebook/react/common:common"),
react_native_target("java/com/facebook/react/devsupport:devsupport"),
react_native_target("java/com/facebook/react/flat:flat"),
react_native_target("java/com/facebook/react/module/model:model"),
react_native_target("java/com/facebook/react/modules/accessibilityinfo:accessibilityinfo"),
react_native_target("java/com/facebook/react/modules/appstate:appstate"),

View File

@ -7,23 +7,11 @@
package com.facebook.react.shell;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import com.facebook.react.LazyReactPackage;
import com.facebook.react.animated.NativeAnimatedModule;
import com.facebook.react.bridge.ModuleSpec;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.flat.FlatARTSurfaceViewManager;
import com.facebook.react.flat.RCTImageViewManager;
import com.facebook.react.flat.RCTModalHostManager;
import com.facebook.react.flat.RCTRawTextManager;
import com.facebook.react.flat.RCTTextInlineImageManager;
import com.facebook.react.flat.RCTTextInputManager;
import com.facebook.react.flat.RCTTextManager;
import com.facebook.react.flat.RCTViewManager;
import com.facebook.react.flat.RCTViewPagerManager;
import com.facebook.react.flat.RCTVirtualTextManager;
import com.facebook.react.module.model.ReactModuleInfoProvider;
import com.facebook.react.modules.accessibilityinfo.AccessibilityInfoModule;
import com.facebook.react.modules.appstate.AppStateModule;
@ -332,21 +320,6 @@ public class MainReactPackage extends LazyReactPackage {
viewManagers.add(new ReactWebViewManager());
viewManagers.add(new SwipeRefreshLayoutManager());
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(reactContext);
boolean useFlatUi = preferences.getBoolean("flat_uiimplementation", false);
if (useFlatUi) {
// Flat managers
viewManagers.add(new FlatARTSurfaceViewManager());
viewManagers.add(new RCTTextInlineImageManager());
viewManagers.add(new RCTImageViewManager());
viewManagers.add(new RCTModalHostManager());
viewManagers.add(new RCTRawTextManager());
viewManagers.add(new RCTTextInputManager());
viewManagers.add(new RCTTextManager());
viewManagers.add(new RCTViewManager());
viewManagers.add(new RCTViewPagerManager());
viewManagers.add(new RCTVirtualTextManager());
} else {
// Native equivalents
viewManagers.add(new ARTSurfaceViewManager());
viewManagers.add(new FrescoBasedReactTextInlineImageViewManager());
@ -358,7 +331,6 @@ public class MainReactPackage extends LazyReactPackage {
viewManagers.add(new ReactViewManager());
viewManagers.add(new ReactViewPagerManager());
viewManagers.add(new ReactVirtualTextViewManager());
}
return viewManagers;
}