/** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ package com.facebook.react.flat; import javax.annotation.Nullable; import java.util.ArrayList; 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; /** * 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 UIOperation { @Override public void execute() { FlatViewGroup.processLayoutRequests(); } } /** * UIOperation that updates DrawCommands for a View defined by reactTag. */ private final class UpdateMountState implements 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 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 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 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 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 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 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 UIOperation { private @Nullable int[] mViewsToDetachAllChildrenFrom; public void setViewsToDetachAllChildrenFrom(int[] viewsToDetachAllChildrenFrom) { mViewsToDetachAllChildrenFrom = viewsToDetachAllChildrenFrom; } @Override public void execute() { mNativeViewHierarchyManager.detachAllChildrenFromViews(mViewsToDetachAllChildrenFrom); } } private final class FindTargetForTouchOperation implements 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 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) { super(reactContext, nativeViewHierarchyManager); 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(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)); } }