mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Back out "Fix keyboard navigation for FlatList with removeClippedSubviews enabled"
Summary: Original commit changeset: 1df7f90066df Original Phabricator Diff: D69618406 Original diff caused unintended scrolling on some interactions that trigger focus searching Changelog: [Internal] Reviewed By: Abbondanzo Differential Revision: D71217683 fbshipit-source-id: faa977ba0fddbf776868d6204d283555e169ba96
This commit is contained in:
committed by
Facebook GitHub Bot
parent
bcd6b5fbd9
commit
fc7385c1ea
@@ -2396,7 +2396,6 @@ public class com/facebook/react/fabric/FabricUIManager : com/facebook/react/brid
|
||||
public fun dispatchCommand (IILcom/facebook/react/bridge/ReadableArray;)V
|
||||
public fun dispatchCommand (IILjava/lang/String;Lcom/facebook/react/bridge/ReadableArray;)V
|
||||
public fun dispatchCommand (ILjava/lang/String;Lcom/facebook/react/bridge/ReadableArray;)V
|
||||
public fun findNextFocusableElementMetrics (III)Lcom/facebook/react/fabric/NextFocusableNode;
|
||||
public fun getColor (I[Ljava/lang/String;)I
|
||||
public fun getEventDispatcher ()Lcom/facebook/react/uimanager/events/EventDispatcher;
|
||||
public fun getPerformanceCounters ()Ljava/util/Map;
|
||||
@@ -2436,12 +2435,6 @@ public final class com/facebook/react/fabric/FabricUIManagerProviderImpl : com/f
|
||||
public fun createUIManager (Lcom/facebook/react/bridge/ReactApplicationContext;)Lcom/facebook/react/bridge/UIManager;
|
||||
}
|
||||
|
||||
public final class com/facebook/react/fabric/NextFocusableNode {
|
||||
public fun <init> (IF)V
|
||||
public final fun getDeltaScroll ()F
|
||||
public final fun getId ()I
|
||||
}
|
||||
|
||||
public class com/facebook/react/fabric/StateWrapperImpl : com/facebook/jni/HybridClassBase, com/facebook/react/uimanager/StateWrapper {
|
||||
public fun destroyState ()V
|
||||
public fun getStateData ()Lcom/facebook/react/bridge/ReadableNativeMap;
|
||||
@@ -6204,7 +6197,6 @@ public class com/facebook/react/views/scroll/ReactHorizontalScrollView : android
|
||||
public fun executeKeyEvent (Landroid/view/KeyEvent;)Z
|
||||
public fun flashScrollIndicators ()V
|
||||
public fun fling (I)V
|
||||
public fun focusSearch (Landroid/view/View;I)Landroid/view/View;
|
||||
public fun getChildVisibleRect (Landroid/view/View;Landroid/graphics/Rect;Landroid/graphics/Point;)Z
|
||||
public fun getClippingRect (Landroid/graphics/Rect;)V
|
||||
public fun getFlingAnimator ()Landroid/animation/ValueAnimator;
|
||||
@@ -6327,7 +6319,6 @@ public class com/facebook/react/views/scroll/ReactScrollView : android/widget/Sc
|
||||
public fun executeKeyEvent (Landroid/view/KeyEvent;)Z
|
||||
public fun flashScrollIndicators ()V
|
||||
public fun fling (I)V
|
||||
public fun focusSearch (Landroid/view/View;I)Landroid/view/View;
|
||||
public fun getChildVisibleRect (Landroid/view/View;Landroid/graphics/Rect;Landroid/graphics/Point;)Z
|
||||
public fun getClippingRect (Landroid/graphics/Rect;)V
|
||||
public fun getFlingAnimator ()Landroid/animation/ValueAnimator;
|
||||
@@ -6448,8 +6439,6 @@ public final class com/facebook/react/views/scroll/ReactScrollViewHelper {
|
||||
public static final fun emitScrollEvent (Landroid/view/ViewGroup;FF)V
|
||||
public static final fun emitScrollMomentumBeginEvent (Landroid/view/ViewGroup;II)V
|
||||
public static final fun emitScrollMomentumEndEvent (Landroid/view/ViewGroup;)V
|
||||
public static final fun findNextClippedElement (Landroid/view/ViewGroup;Landroid/view/View;ILcom/facebook/react/bridge/ReactContext;)Landroid/view/View;
|
||||
public static final fun findNextFocusableView (Landroid/view/ViewGroup;Landroid/view/View;IZ)Landroid/view/View;
|
||||
public static final fun forceUpdateState (Landroid/view/ViewGroup;)V
|
||||
public static final fun getDefaultScrollAnimationDuration (Landroid/content/Context;)I
|
||||
public static final fun getNextFlingStartValue (Landroid/view/ViewGroup;III)I
|
||||
@@ -6459,7 +6448,6 @@ public final class com/facebook/react/views/scroll/ReactScrollViewHelper {
|
||||
public final fun registerFlingAnimator (Landroid/view/ViewGroup;)V
|
||||
public static final fun removeLayoutChangeListener (Lcom/facebook/react/views/scroll/ReactScrollViewHelper$LayoutChangeListener;)V
|
||||
public static final fun removeScrollListener (Lcom/facebook/react/views/scroll/ReactScrollViewHelper$ScrollListener;)V
|
||||
public static final fun resolveAbsoluteDirection (IZI)I
|
||||
public static final fun smoothScrollTo (Landroid/view/ViewGroup;II)V
|
||||
public static final fun updateFabricScrollState (Landroid/view/ViewGroup;)V
|
||||
public final fun updateFabricScrollState (Landroid/view/ViewGroup;II)V
|
||||
|
||||
-46
@@ -29,7 +29,6 @@ import androidx.annotation.AnyThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.UiThread;
|
||||
import androidx.core.view.ViewCompat.FocusRealDirection;
|
||||
import com.facebook.common.logging.FLog;
|
||||
import com.facebook.infer.annotation.ThreadConfined;
|
||||
import com.facebook.proguard.annotations.DoNotStripAny;
|
||||
@@ -261,51 +260,6 @@ public class FabricUIManager
|
||||
return rootTag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the next focusable element's id and position relative to the parent from the shadow tree
|
||||
* based on the current focusable element and the direction.
|
||||
*
|
||||
* @return A NextFocusableNode object where the 'id' is the reactId/Tag of the next focusable
|
||||
* view, and 'deltaScroll' is the scroll delta needed to make the view visible on the screen.
|
||||
* Returns null if no valid node is found.
|
||||
*/
|
||||
public @Nullable NextFocusableNode findNextFocusableElementMetrics(
|
||||
int parentTag, int focusedTag, @FocusRealDirection int direction) {
|
||||
if (mBinding == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int generalizedDirection;
|
||||
|
||||
switch (direction) {
|
||||
case View.FOCUS_DOWN:
|
||||
generalizedDirection = 0;
|
||||
break;
|
||||
case View.FOCUS_UP:
|
||||
generalizedDirection = 1;
|
||||
break;
|
||||
case View.FOCUS_RIGHT:
|
||||
generalizedDirection = 2;
|
||||
break;
|
||||
case View.FOCUS_LEFT:
|
||||
generalizedDirection = 3;
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
float[] serializedNextFocusableNodeMetrics =
|
||||
mBinding.findNextFocusableElementMetrics(parentTag, focusedTag, generalizedDirection);
|
||||
|
||||
if (serializedNextFocusableNodeMetrics == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new NextFocusableNode(
|
||||
(int) serializedNextFocusableNodeMetrics[0], serializedNextFocusableNodeMetrics[1]);
|
||||
}
|
||||
|
||||
@Override
|
||||
@AnyThread
|
||||
@ThreadConfined(ANY)
|
||||
|
||||
-6
@@ -55,12 +55,6 @@ internal class FabricUIManagerBinding : HybridClassBase() {
|
||||
isMountable: Boolean
|
||||
)
|
||||
|
||||
external fun findNextFocusableElementMetrics(
|
||||
parentTag: Int,
|
||||
focusedTag: Int,
|
||||
direction: Int
|
||||
): FloatArray
|
||||
|
||||
external fun stopSurface(surfaceId: Int)
|
||||
|
||||
external fun stopSurfaceWithSurfaceHandler(surfaceHandler: SurfaceHandlerBinding)
|
||||
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* 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.fabric
|
||||
|
||||
public class NextFocusableNode(public val id: Int, public val deltaScroll: Float)
|
||||
-13
@@ -11,7 +11,6 @@ import static com.facebook.react.views.scroll.ReactScrollViewHelper.SNAP_ALIGNME
|
||||
import static com.facebook.react.views.scroll.ReactScrollViewHelper.SNAP_ALIGNMENT_DISABLED;
|
||||
import static com.facebook.react.views.scroll.ReactScrollViewHelper.SNAP_ALIGNMENT_END;
|
||||
import static com.facebook.react.views.scroll.ReactScrollViewHelper.SNAP_ALIGNMENT_START;
|
||||
import static com.facebook.react.views.scroll.ReactScrollViewHelper.findNextFocusableView;
|
||||
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.animation.ValueAnimator;
|
||||
@@ -32,7 +31,6 @@ import android.widget.HorizontalScrollView;
|
||||
import android.widget.OverScroller;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.core.view.ViewCompat.FocusRealDirection;
|
||||
import com.facebook.common.logging.FLog;
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.infer.annotation.Nullsafe;
|
||||
@@ -774,17 +772,6 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable View focusSearch(View focused, @FocusRealDirection int direction) {
|
||||
@Nullable View nextfocusableView = findNextFocusableView(this, focused, direction, true);
|
||||
|
||||
if (nextfocusableView != null) {
|
||||
return nextfocusableView;
|
||||
}
|
||||
|
||||
return super.focusSearch(focused, direction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateClippingRect() {
|
||||
if (!mRemoveClippedSubviews) {
|
||||
|
||||
-14
@@ -11,7 +11,6 @@ import static com.facebook.react.views.scroll.ReactScrollViewHelper.SNAP_ALIGNME
|
||||
import static com.facebook.react.views.scroll.ReactScrollViewHelper.SNAP_ALIGNMENT_DISABLED;
|
||||
import static com.facebook.react.views.scroll.ReactScrollViewHelper.SNAP_ALIGNMENT_END;
|
||||
import static com.facebook.react.views.scroll.ReactScrollViewHelper.SNAP_ALIGNMENT_START;
|
||||
import static com.facebook.react.views.scroll.ReactScrollViewHelper.findNextFocusableView;
|
||||
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.animation.ValueAnimator;
|
||||
@@ -32,7 +31,6 @@ import android.widget.ScrollView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.core.view.ViewCompat.FocusRealDirection;
|
||||
import com.facebook.common.logging.FLog;
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.infer.annotation.Nullsafe;
|
||||
@@ -361,18 +359,6 @@ public class ReactScrollView extends ScrollView
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable View focusSearch(View focused, @FocusRealDirection int direction) {
|
||||
|
||||
@Nullable View nextfocusableView = findNextFocusableView(this, focused, direction, false);
|
||||
|
||||
if (nextfocusableView != null) {
|
||||
return nextfocusableView;
|
||||
}
|
||||
|
||||
return super.focusSearch(focused, direction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Since ReactScrollView handles layout changes on JS side, it does not call super.onlayout due to
|
||||
* which mIsLayoutDirty flag in ScrollView remains true and prevents scrolling to child when
|
||||
|
||||
-87
@@ -11,19 +11,15 @@ import android.animation.Animator
|
||||
import android.animation.ValueAnimator
|
||||
import android.content.Context
|
||||
import android.graphics.Point
|
||||
import android.view.FocusFinder
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.OverScroller
|
||||
import androidx.core.view.ViewCompat.FocusRealDirection
|
||||
import com.facebook.common.logging.FLog
|
||||
import com.facebook.react.bridge.ReactContext
|
||||
import com.facebook.react.bridge.WritableMap
|
||||
import com.facebook.react.bridge.WritableNativeMap
|
||||
import com.facebook.react.common.ReactConstants
|
||||
import com.facebook.react.fabric.FabricUIManager
|
||||
import com.facebook.react.uimanager.PixelUtil.toDIPFromPixel
|
||||
import com.facebook.react.uimanager.PixelUtil.toPixelFromDIP
|
||||
import com.facebook.react.uimanager.StateWrapper
|
||||
import com.facebook.react.uimanager.UIManagerHelper
|
||||
import com.facebook.react.uimanager.common.UIManagerType
|
||||
@@ -466,89 +462,6 @@ public object ReactScrollViewHelper {
|
||||
return Point(scroller.finalX, scroller.finalY)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
public fun findNextFocusableView(
|
||||
host: ViewGroup,
|
||||
focused: View,
|
||||
@FocusRealDirection direction: Int,
|
||||
horizontal: Boolean
|
||||
): View? {
|
||||
val absDir = resolveAbsoluteDirection(direction, horizontal, host.getLayoutDirection())
|
||||
|
||||
/*
|
||||
* Check if we can focus the next element in the absolute direction within the ScrollView,
|
||||
* if we can't, look into the shadow tree to find the next focusable element
|
||||
*/
|
||||
val ff = FocusFinder.getInstance()
|
||||
val result = ff.findNextFocus(host, focused, absDir)
|
||||
|
||||
if (result != null) {
|
||||
return result
|
||||
}
|
||||
|
||||
/*
|
||||
* Attempt to focus the next focusable but clipped element on the list if there is one, since
|
||||
* the view is clipped it is not currently in the hierarchy so we scroll it into view and then
|
||||
* focus it.
|
||||
*/
|
||||
return findNextClippedElement(host, focused, absDir, host.context as ReactContext)
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to focus the next element in the specified direction within the scrollView.
|
||||
*
|
||||
* @return true if a new element was successfully focused, otherwise false.
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun findNextClippedElement(
|
||||
scrollView: ViewGroup,
|
||||
focused: View,
|
||||
@FocusRealDirection direction: Int,
|
||||
context: ReactContext,
|
||||
): View? {
|
||||
val uimanager = UIManagerHelper.getUIManager(context, UIManagerType.FABRIC) ?: return null
|
||||
|
||||
val nextFocusableViewMetrics =
|
||||
(uimanager as FabricUIManager).findNextFocusableElementMetrics(
|
||||
scrollView.id, focused.id, direction)
|
||||
|
||||
if (nextFocusableViewMetrics != null) {
|
||||
|
||||
when (direction) {
|
||||
View.FOCUS_UP,
|
||||
View.FOCUS_DOWN -> {
|
||||
scrollView.scrollBy(0, toPixelFromDIP(nextFocusableViewMetrics.deltaScroll).toInt())
|
||||
}
|
||||
View.FOCUS_RIGHT,
|
||||
View.FOCUS_LEFT -> {
|
||||
scrollView.scrollBy(toPixelFromDIP(nextFocusableViewMetrics.deltaScroll).toInt(), 0)
|
||||
}
|
||||
else -> return null
|
||||
}
|
||||
return scrollView.findViewById(nextFocusableViewMetrics.id)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
public fun resolveAbsoluteDirection(
|
||||
@FocusRealDirection direction: Int,
|
||||
horizontal: Boolean,
|
||||
layoutDirection: Int
|
||||
): Int {
|
||||
val rtl: Boolean = layoutDirection == View.LAYOUT_DIRECTION_RTL
|
||||
|
||||
return if (direction == View.FOCUS_FORWARD || direction == View.FOCUS_BACKWARD) {
|
||||
if (horizontal) {
|
||||
if ((direction == View.FOCUS_FORWARD) != rtl) View.FOCUS_RIGHT else View.FOCUS_LEFT
|
||||
} else {
|
||||
if (direction == View.FOCUS_FORWARD) View.FOCUS_DOWN else View.FOCUS_UP
|
||||
}
|
||||
} else {
|
||||
direction
|
||||
}
|
||||
}
|
||||
|
||||
public interface ScrollListener {
|
||||
public fun onScroll(
|
||||
scrollView: ViewGroup?,
|
||||
|
||||
-113
@@ -12,7 +12,6 @@
|
||||
#include "EventBeatManager.h"
|
||||
#include "EventEmitterWrapper.h"
|
||||
#include "FabricMountingManager.h"
|
||||
#include "FocusOrderingHelper.h"
|
||||
|
||||
#include <cxxreact/TraceSection.h>
|
||||
#include <fbjni/fbjni.h>
|
||||
@@ -196,115 +195,6 @@ void FabricUIManagerBinding::startSurface(
|
||||
}
|
||||
}
|
||||
|
||||
jfloatArray FabricUIManagerBinding::findNextFocusableElementMetrics(
|
||||
jint parentTag,
|
||||
jint focusedTag,
|
||||
jint direction) {
|
||||
/*
|
||||
* Holds the values of the next focusable element
|
||||
* [0] - tag
|
||||
* [1] - delta
|
||||
*/
|
||||
std::array<float, 2> nodeValues{-1, -1};
|
||||
|
||||
JNIEnv* env = jni::Environment::current();
|
||||
|
||||
ShadowNode::Shared nextNode;
|
||||
|
||||
auto nodeValuesSize = static_cast<jsize>(nodeValues.size());
|
||||
jfloatArray result = env->NewFloatArray(nodeValuesSize);
|
||||
|
||||
std::optional<FocusDirection> focusDirection =
|
||||
FocusOrderingHelper::resolveFocusDirection(direction);
|
||||
|
||||
if (!focusDirection.has_value()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ShadowNode::Shared parentShadowNode;
|
||||
ShadowNode::Shared focusedShadowNode;
|
||||
std::shared_ptr<UIManager> uimanager = getScheduler()->getUIManager();
|
||||
|
||||
parentShadowNode = uimanager->findShadowNodeByTag_DEPRECATED(parentTag);
|
||||
|
||||
if (parentShadowNode == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
focusedShadowNode = FocusOrderingHelper::findShadowNodeByTagRecursively(
|
||||
parentShadowNode, focusedTag);
|
||||
|
||||
if (focusedShadowNode == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
LayoutMetrics childLayoutMetrics = uimanager->getRelativeLayoutMetrics(
|
||||
*focusedShadowNode, parentShadowNode.get(), {.includeTransform = true});
|
||||
|
||||
/*
|
||||
* Set currentDelta to the origin of the focused node
|
||||
* Set nextDelta to the max int value in the direction we want to go (down =
|
||||
* INT_MIN, up = INT_MAX)
|
||||
*/
|
||||
auto [currentDelta, nextDelta] = FocusOrderingHelper::initScrollDeltas(
|
||||
focusDirection.value(), childLayoutMetrics.frame.origin);
|
||||
|
||||
/*
|
||||
* Traverse the tree recursively to find the next focusable element in the
|
||||
* given direction
|
||||
*/
|
||||
FocusOrderingHelper::traverseAndUpdateNextFocusableElementMetrics(
|
||||
parentShadowNode,
|
||||
focusedShadowNode,
|
||||
parentShadowNode,
|
||||
focusDirection.value(),
|
||||
*uimanager,
|
||||
currentDelta,
|
||||
nextDelta,
|
||||
nextNode);
|
||||
|
||||
if (nextNode == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
LayoutMetrics nextNodeLayoutMetrics = uimanager->getRelativeLayoutMetrics(
|
||||
*nextNode, parentShadowNode.get(), {.includeTransform = true});
|
||||
|
||||
LayoutMetrics parentNodeLayoutMetrics = uimanager->getRelativeLayoutMetrics(
|
||||
*parentShadowNode, nullptr, {.includeTransform = true});
|
||||
|
||||
nodeValues[0] = static_cast<float>(nextNode->getTag());
|
||||
|
||||
/*
|
||||
* In the end we want to scroll enough to make the next node visible
|
||||
* When going up or right we want delta to be the origin of the next node
|
||||
* When going down or left we want delta to be the end of the next node
|
||||
*/
|
||||
switch (focusDirection.value()) {
|
||||
case FocusDirection::FocusDown:
|
||||
nextDelta = (nextNodeLayoutMetrics.frame.origin.y -
|
||||
parentNodeLayoutMetrics.frame.size.height) +
|
||||
nextNodeLayoutMetrics.frame.size.height;
|
||||
nodeValues[1] = nextDelta;
|
||||
break;
|
||||
case FocusDirection::FocusRight:
|
||||
nextDelta = (nextNodeLayoutMetrics.frame.origin.x -
|
||||
parentNodeLayoutMetrics.frame.size.width) +
|
||||
nextNodeLayoutMetrics.frame.size.width;
|
||||
nodeValues[1] = nextDelta;
|
||||
break;
|
||||
case FocusDirection::FocusLeft:
|
||||
case FocusDirection::FocusUp:
|
||||
nodeValues[1] = nextDelta;
|
||||
break;
|
||||
}
|
||||
|
||||
// Build the jfloatArray with the updates nodeValues
|
||||
env->SetFloatArrayRegion(result, 0, nodeValuesSize, nodeValues.data());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Used by non-bridgeless+Fabric
|
||||
void FabricUIManagerBinding::startSurfaceWithConstraints(
|
||||
jint surfaceId,
|
||||
@@ -766,9 +656,6 @@ void FabricUIManagerBinding::registerNatives() {
|
||||
makeNativeMethod(
|
||||
"stopSurfaceWithSurfaceHandler",
|
||||
FabricUIManagerBinding::stopSurfaceWithSurfaceHandler),
|
||||
makeNativeMethod(
|
||||
"findNextFocusableElementMetrics",
|
||||
FabricUIManagerBinding::findNextFocusableElementMetrics),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -128,11 +128,6 @@ class FabricUIManagerBinding : public jni::HybridClass<FabricUIManagerBinding>,
|
||||
|
||||
void reportMount(SurfaceId surfaceId);
|
||||
|
||||
jfloatArray findNextFocusableElementMetrics(
|
||||
jint parentTag,
|
||||
jint focusedTag,
|
||||
jint direction);
|
||||
|
||||
void uninstallFabricUIManager();
|
||||
|
||||
// Private member variables
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include "FocusOrderingHelper.h"
|
||||
#include <react/renderer/uimanager/UIManager.h>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
void FocusOrderingHelper::traverseAndUpdateNextFocusableElementMetrics(
|
||||
const ShadowNode::Shared& parentShadowNode,
|
||||
const ShadowNode::Shared& focusedShadowNode,
|
||||
const ShadowNode::Shared& currNode,
|
||||
FocusDirection focusDirection,
|
||||
const UIManager& uimanager,
|
||||
float& currentDelta,
|
||||
float& nextDelta,
|
||||
ShadowNode::Shared& nextNode) {
|
||||
const auto* props =
|
||||
dynamic_cast<const ViewProps*>(currNode->getProps().get());
|
||||
|
||||
// We only care about focusable elements since only they can be both
|
||||
// focused and present in the hierarchy
|
||||
if (props != nullptr && props->focusable) {
|
||||
LayoutMetrics nodeLayoutMetrics = uimanager.getRelativeLayoutMetrics(
|
||||
*currNode, parentShadowNode.get(), {.includeTransform = true});
|
||||
|
||||
switch (focusDirection) {
|
||||
case FocusDirection::FocusDown:
|
||||
if (nodeLayoutMetrics.frame.origin.y > currentDelta &&
|
||||
nodeLayoutMetrics.frame.origin.y < nextDelta &&
|
||||
currNode != focusedShadowNode) {
|
||||
nextNode = currNode;
|
||||
nextDelta = nodeLayoutMetrics.frame.origin.y;
|
||||
}
|
||||
break;
|
||||
case FocusDirection::FocusUp:
|
||||
if (nodeLayoutMetrics.frame.origin.y < currentDelta &&
|
||||
nodeLayoutMetrics.frame.origin.y > nextDelta &&
|
||||
currNode != focusedShadowNode) {
|
||||
nextNode = currNode;
|
||||
nextDelta = nodeLayoutMetrics.frame.origin.y;
|
||||
}
|
||||
break;
|
||||
case FocusDirection::FocusRight:
|
||||
if (nodeLayoutMetrics.frame.origin.x > currentDelta &&
|
||||
nodeLayoutMetrics.frame.origin.x < nextDelta &&
|
||||
currNode != focusedShadowNode) {
|
||||
nextNode = currNode;
|
||||
nextDelta = nodeLayoutMetrics.frame.origin.x;
|
||||
}
|
||||
break;
|
||||
case FocusDirection::FocusLeft:
|
||||
if (nodeLayoutMetrics.frame.origin.x < currentDelta &&
|
||||
nodeLayoutMetrics.frame.origin.x > nextDelta &&
|
||||
currNode != focusedShadowNode) {
|
||||
nextNode = currNode;
|
||||
nextDelta = nodeLayoutMetrics.frame.origin.x;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& child : currNode->getChildren()) {
|
||||
if (child->getTraits().check(ShadowNodeTraits::Trait::RootNodeKind)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
traverseAndUpdateNextFocusableElementMetrics(
|
||||
parentShadowNode,
|
||||
focusedShadowNode,
|
||||
child,
|
||||
focusDirection,
|
||||
uimanager,
|
||||
currentDelta,
|
||||
nextDelta,
|
||||
nextNode);
|
||||
};
|
||||
};
|
||||
|
||||
ShadowNode::Shared FocusOrderingHelper::findShadowNodeByTagRecursively(
|
||||
const ShadowNode::Shared& parentShadowNode,
|
||||
Tag tag) {
|
||||
if (parentShadowNode->getTag() == tag) {
|
||||
return parentShadowNode;
|
||||
}
|
||||
|
||||
for (auto& shadowNode : parentShadowNode->getChildren()) {
|
||||
if (auto result = findShadowNodeByTagRecursively(shadowNode, tag)) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::optional<FocusDirection> FocusOrderingHelper::resolveFocusDirection(
|
||||
int direction) {
|
||||
switch (static_cast<FocusDirection>(direction)) {
|
||||
case FocusDirection::FocusDown:
|
||||
case FocusDirection::FocusUp:
|
||||
case FocusDirection::FocusRight:
|
||||
case FocusDirection::FocusLeft:
|
||||
return static_cast<FocusDirection>(direction);
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::tuple<float, float> FocusOrderingHelper::initScrollDeltas(
|
||||
FocusDirection focusDirection,
|
||||
Point refPoint) {
|
||||
float currentDelta = 0;
|
||||
float nextDelta = 0;
|
||||
|
||||
switch (focusDirection) {
|
||||
case FocusDirection::FocusDown:
|
||||
currentDelta = refPoint.y;
|
||||
nextDelta = std::numeric_limits<float>::max();
|
||||
break;
|
||||
case FocusDirection::FocusUp:
|
||||
currentDelta = refPoint.y;
|
||||
nextDelta = -std::numeric_limits<float>::max();
|
||||
break;
|
||||
case FocusDirection::FocusRight:
|
||||
currentDelta = refPoint.x;
|
||||
nextDelta = std::numeric_limits<float>::max();
|
||||
break;
|
||||
case FocusDirection::FocusLeft:
|
||||
currentDelta = refPoint.x;
|
||||
nextDelta = -std::numeric_limits<float>::max();
|
||||
break;
|
||||
}
|
||||
return std::make_tuple(currentDelta, nextDelta);
|
||||
}
|
||||
} // namespace facebook::react
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include <react/renderer/uimanager/UIManager.h>
|
||||
#include "FabricUIManagerBinding.h"
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
enum class FocusDirection {
|
||||
FocusDown = 0,
|
||||
FocusUp = 1,
|
||||
FocusRight = 2,
|
||||
FocusLeft = 3,
|
||||
};
|
||||
|
||||
class FocusOrderingHelper {
|
||||
public:
|
||||
static void traverseAndUpdateNextFocusableElementMetrics(
|
||||
const ShadowNode::Shared& parentShadowNode,
|
||||
const ShadowNode::Shared& focusedShadowNode,
|
||||
const ShadowNode::Shared& currNode,
|
||||
FocusDirection focusDirection,
|
||||
const UIManager& uimanager,
|
||||
float& currentDelta,
|
||||
float& nextDelta,
|
||||
ShadowNode::Shared& nextNode);
|
||||
|
||||
static ShadowNode::Shared findShadowNodeByTagRecursively(
|
||||
const ShadowNode::Shared& parentShadowNode,
|
||||
Tag tag);
|
||||
|
||||
static std::optional<FocusDirection> resolveFocusDirection(int direction);
|
||||
|
||||
static std::tuple<float, float> initScrollDeltas(
|
||||
FocusDirection focusDirection,
|
||||
Point refPoint);
|
||||
};
|
||||
} // namespace facebook::react
|
||||
Reference in New Issue
Block a user