diff --git a/android/shadowlist/jni/OnLoad.cpp b/android/shadowlist/jni/OnLoad.cpp index ced8598..f6e2a7d 100644 --- a/android/shadowlist/jni/OnLoad.cpp +++ b/android/shadowlist/jni/OnLoad.cpp @@ -2,14 +2,16 @@ #include "ShadowlistModule.h" #include "SLRuntimeManager.h" +#include "SLModuleJSI.h" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) { return facebook::jni::initialize(vm, [] { - facebook::react::ShadowlistModule::registerNatives(); + facebook::react::ShadowlistModule::registerNatives(); }); } -extern "C" JNIEXPORT void JNICALL Java_com_shadowlist_ShadowlistModule_injectJSIBindings(JNIEnv *env, jobject thiz, jlong jsiRuntime) { +extern "C" JNIEXPORT void JNICALL Java_com_shadowlist_ShadowlistModule_injectJSIBindings(JNIEnv *env, jobject thiz, jobject jsiRuntime) { jsi::Runtime* runtime = reinterpret_cast(jsiRuntime); SLRuntimeManager::getInstance().setRuntime(runtime); + SLModuleJSI::install(*runtime); } diff --git a/android/src/main/java/com/shadowlist/SLContainer.java b/android/src/main/java/com/shadowlist/SLContainer.java index e7ee054..fc97f02 100644 --- a/android/src/main/java/com/shadowlist/SLContainer.java +++ b/android/src/main/java/com/shadowlist/SLContainer.java @@ -13,12 +13,14 @@ import com.facebook.react.common.mapbuffer.MapBuffer; import com.facebook.react.uimanager.PixelUtil; import com.facebook.react.uimanager.StateWrapper; +import com.facebook.react.views.scroll.ReactHorizontalScrollView; +import com.facebook.react.views.scroll.ReactScrollView; import com.facebook.react.views.view.ReactViewGroup; public class SLContainer extends ReactViewGroup { private boolean mOrientation; - private HorizontalScrollView mScrollContainerHorizontal; - private ScrollView mScrollContainerVertical; + private ReactHorizontalScrollView mScrollContainerHorizontal; + private ReactScrollView mScrollContainerVertical; private ReactViewGroup mScrollContent; private SLContainerManager.OnStartReachedHandler mOnStartReachedHandler; private SLContainerManager.OnEndReachedHandler mOnEndReachedHandler; @@ -37,8 +39,8 @@ public SLContainer(Context context, AttributeSet attrs) { } private void init(Context context) { - mScrollContainerHorizontal = new HorizontalScrollView(context); - mScrollContainerVertical = new ScrollView(context); + mScrollContainerHorizontal = new ReactHorizontalScrollView(context); + mScrollContainerVertical = new ReactScrollView(context); mScrollContent = new ReactViewGroup(context); @@ -48,12 +50,14 @@ private void init(Context context) { stateMapBuffer.putDouble("scrollPositionTop", PixelUtil.toDIPFromPixel(scrollY)); mStateWrapper.updateState(stateMapBuffer); }; + OnScrollChangeListener scrollListenerHorizontal = (v, scrollX, scrollY, oldScrollX, oldScrollY) -> { WritableMap stateMapBuffer = new WritableNativeMap(); stateMapBuffer.putDouble("scrollPositionLeft", PixelUtil.toDIPFromPixel(oldScrollX)); stateMapBuffer.putDouble("scrollPositionTop", PixelUtil.toDIPFromPixel(scrollY)); mStateWrapper.updateState(stateMapBuffer); }; + mScrollContainerVertical.setOnScrollChangeListener(scrollListenerVertical); mScrollContainerVertical.setVerticalScrollBarEnabled(true); mScrollContainerHorizontal.setOnScrollChangeListener(scrollListenerHorizontal); diff --git a/cpp/react/renderer/components/RNShadowlistSpec/SLModuleJSI.cpp b/cpp/react/renderer/components/RNShadowlistSpec/SLModuleJSI.cpp index 0c9b439..d6f8a2f 100644 --- a/cpp/react/renderer/components/RNShadowlistSpec/SLModuleJSI.cpp +++ b/cpp/react/renderer/components/RNShadowlistSpec/SLModuleJSI.cpp @@ -5,6 +5,22 @@ namespace facebook::react { using namespace facebook; +void SLModuleJSI::install(facebook::jsi::Runtime &runtime) { + auto getRegistryElementMapping = facebook::jsi::Function::createFromHostFunction( + runtime, + facebook::jsi::PropNameID::forAscii(runtime, "__NATIVE_getRegistryElementMapping"), + 1, + [&](facebook::jsi::Runtime &runtime, + const facebook::jsi::Value &thisValue, + const facebook::jsi::Value *arguments, + size_t count) -> facebook::jsi::Value + { + int elementFamilyTag = SLRuntimeManager::getInstance().getTag(arguments[0].asNumber()); + return facebook::jsi::Value(elementFamilyTag); + }); + runtime.global().setProperty(runtime, "__NATIVE_getRegistryElementMapping", std::move(getRegistryElementMapping)); +} + void SLModuleJSI::install(facebook::jsi::Runtime &runtime, std::shared_ptr &commitHook) { auto registerContainerFamily = facebook::jsi::Function::createFromHostFunction( runtime, @@ -69,20 +85,6 @@ void SLModuleJSI::install(facebook::jsi::Runtime &runtime, std::shared_ptr facebook::jsi::Value - { - int elementFamilyTag = SLRuntimeManager::getInstance().getTag(arguments[0].asNumber()); - return facebook::jsi::Value(elementFamilyTag); - }); - runtime.global().setProperty(runtime, "__NATIVE_getRegistryElementMapping", std::move(getRegistryElementMapping)); } } diff --git a/cpp/react/renderer/components/RNShadowlistSpec/SLModuleJSI.h b/cpp/react/renderer/components/RNShadowlistSpec/SLModuleJSI.h index 303f8eb..a390c79 100644 --- a/cpp/react/renderer/components/RNShadowlistSpec/SLModuleJSI.h +++ b/cpp/react/renderer/components/RNShadowlistSpec/SLModuleJSI.h @@ -4,6 +4,7 @@ namespace facebook::react { class SLModuleJSI { public: + static void install(facebook::jsi::Runtime &runtime); static void install(facebook::jsi::Runtime &runtime, std::shared_ptr &commitHook); }; diff --git a/cpp/react/renderer/components/RNShadowlistSpec/SLTemplate.cpp b/cpp/react/renderer/components/RNShadowlistSpec/SLTemplate.cpp index 89a3874..2748cf9 100644 --- a/cpp/react/renderer/components/RNShadowlistSpec/SLTemplate.cpp +++ b/cpp/react/renderer/components/RNShadowlistSpec/SLTemplate.cpp @@ -5,12 +5,16 @@ namespace facebook::react { -int nextFamilyTag = -2; +/* + * Following offset of elements should probably be enough for now + * However this should be revisited and optimized with different strategy later + */ +int nextFamilyTag = 524288; auto adjustFamilyTag = [](int tag) { - const int MIN_TAG_VALUE = -2e9; - const int CLAMPED_TAG = -2; - return tag < MIN_TAG_VALUE ? CLAMPED_TAG : tag - 2; + const int MAX_TAG_VALUE = std::numeric_limits::max(); + const int CLAMPED_TAG = 2; + return tag > MAX_TAG_VALUE ? CLAMPED_TAG : tag + 2; }; static void updateRawTextProps(const SLContainerProps::SLContainerDataItem &elementData, const std::shared_ptr &nextShadowNode, const ShadowNode::Shared &shadowNode) { @@ -47,22 +51,23 @@ ShadowNode::Unshared SLTemplate::cloneShadowNodeTree(const int& elementDataIndex *SLRuntimeManager::getInstance().getRuntime(), shadowNode->getInstanceHandle(*SLRuntimeManager::getInstance().getRuntime()), nextFamilyTag); - auto const fragment = ShadowNodeFamilyFragment{nextFamilyTag, shadowNode->getSurfaceId(), instanceHandle}; - auto const family = componentDescriptor.createFamily(fragment); - + + auto const family = componentDescriptor.createFamily({nextFamilyTag, shadowNode->getSurfaceId(), instanceHandle}); + #ifdef ANDROID - auto const props = componentDescriptor.cloneProps(propsParserContext, shadowNode->getProps(), RawProps(shadowNode->getProps()->rawProps)); + auto const nextProps = componentDescriptor.cloneProps(propsParserContext, shadowNode->getProps(), RawProps(shadowNode->getProps()->rawProps)); #else - auto const props = componentDescriptor.cloneProps(propsParserContext, shadowNode->getProps(), {}); + auto const nextProps = componentDescriptor.cloneProps(propsParserContext, shadowNode->getProps(), {}); #endif - auto const state = componentDescriptor.createInitialState(props, family); - auto const nextShadowNode = componentDescriptor.createShadowNode( - ShadowNodeFragment{props, ShadowNodeFragment::childrenPlaceholder(), state}, family); + auto const nextState = componentDescriptor.createInitialState(nextProps, family); + auto const nextShadowNode = componentDescriptor.createShadowNode({nextProps, ShadowNodeFragment::childrenPlaceholder(), nextState}, family); updateRawTextProps(elementData, nextShadowNode, shadowNode); updateImageProps(elementData, nextShadowNode, shadowNode); + nextShadowNode->setMounted(true); + for (const auto &childShadowNode : shadowNode->getChildren()) { auto const clonedChildShadowNode = cloneShadowNodeTree(elementDataIndex, elementData, childShadowNode); componentDescriptor.appendChild(nextShadowNode, clonedChildShadowNode); diff --git a/cpp/react/renderer/components/SLContainerSpec/SLContainerShadowNode.cpp b/cpp/react/renderer/components/SLContainerSpec/SLContainerShadowNode.cpp index f46e290..a27a207 100644 --- a/cpp/react/renderer/components/SLContainerSpec/SLContainerShadowNode.cpp +++ b/cpp/react/renderer/components/SLContainerSpec/SLContainerShadowNode.cpp @@ -22,7 +22,7 @@ void SLContainerShadowNode::layout(LayoutContext layoutContext) { auto &templateRegistry = elementShadowNodeTemplateRegistry[getTag()]; auto &componentRegistry = elementShadowNodeComponentRegistry[getTag()]; - + auto &props = getConcreteProps(); auto nextStateData = getStateData(); @@ -34,6 +34,7 @@ void SLContainerShadowNode::layout(LayoutContext layoutContext) { const int viewportOffset = 1000; nextStateData.scrollPositionUpdated = false; + nextStateData.scrollContainer = getLayoutMetrics().frame.size; if (!nextStateData.childrenMeasurementsTree.size() && props.initialScrollIndex) { nextStateData.scrollIndex = props.initialScrollIndex; @@ -63,7 +64,7 @@ void SLContainerShadowNode::layout(LayoutContext layoutContext) { } /* - * If data is prepended, extend the measurements tree from the beginning + * If data is prepended, extend the measurements tree from the beginning * to match the new data size. If data is appended, resize the tree at the end */ if (elementsDataPrepended) { @@ -80,7 +81,7 @@ void SLContainerShadowNode::layout(LayoutContext layoutContext) { } else { nextStateData.childrenMeasurementsTree.resize(elementsDataSize); } - + /* * Static templates measurements, incl. Header, Empty, Footer */ @@ -153,8 +154,6 @@ void SLContainerShadowNode::layout(LayoutContext layoutContext) { containerShadowNodeChildren->push_back(componentRegistry["ListHeaderComponentUniqueId"]); } - - /* * Calculate sequence of indices above and below the current scroll index */ @@ -185,18 +184,18 @@ void SLContainerShadowNode::layout(LayoutContext layoutContext) { for (ComponentRegistryItem componentRegistryItem : scrollContentAboveComponents) { auto elementMetrics = adjustElement( { - .x = (componentRegistryItem.index % props.numColumns) * (getLayoutMetrics().frame.size.width / props.numColumns), + .x = (componentRegistryItem.index % props.numColumns) * (nextStateData.scrollContainer.width / props.numColumns), .y = scrollContentAboveOffset.get(componentRegistryItem.index % props.numColumns) + scrollContentBelowOffset.get(componentRegistryItem.index % props.numColumns) }, componentRegistry[componentRegistryItem.elementDataUniqueKey]); scrollContentAboveOffset.add(componentRegistryItem.index % props.numColumns, componentRegistryItem.size); scrollContentAboveIndex = componentRegistryItem.index; - + int scrollPosition = nextStateData.scrollPositionUpdated ? ( scrollContentAboveOffset.get(componentRegistryItem.index % props.numColumns) + getRelativePointFromPoint(nextStateData.scrollPosition) - nextStateData.templateMeasurementsTree[0] ) : getRelativePointFromPoint(nextStateData.scrollPosition); - + if (getRelativePointFromPoint(elementMetrics.frame.origin) <= (scrollPosition + getRelativeSizeFromSize(nextStateData.scrollContainer) + viewportOffset) && (getRelativePointFromPoint(elementMetrics.frame.origin) + getRelativeSizeFromSize(elementMetrics.frame.size)) >= (scrollPosition - viewportOffset)) { containerShadowNodeChildren->push_back(componentRegistry[componentRegistryItem.elementDataUniqueKey]); @@ -211,18 +210,18 @@ void SLContainerShadowNode::layout(LayoutContext layoutContext) { auto elementShadowNodeLayoutable = std::static_pointer_cast(componentRegistry[componentRegistryItem.elementDataUniqueKey]); auto elementMetrics = adjustElement( { - .x = (componentRegistryItem.index % props.numColumns) * (getLayoutMetrics().frame.size.width / props.numColumns), + .x = (componentRegistryItem.index % props.numColumns) * (nextStateData.scrollContainer.width / props.numColumns), .y = scrollContentAboveOffset.get(componentRegistryItem.index % props.numColumns) + scrollContentBelowOffset.get(componentRegistryItem.index % props.numColumns) }, componentRegistry[componentRegistryItem.elementDataUniqueKey]); scrollContentBelowOffset.add(componentRegistryItem.index % props.numColumns, componentRegistryItem.size); scrollContentBelowIndex = componentRegistryItem.index; - + int scrollPosition = nextStateData.scrollPositionUpdated ? ( scrollContentAboveOffset.get(componentRegistryItem.index % props.numColumns) + getRelativePointFromPoint(nextStateData.scrollPosition) - nextStateData.templateMeasurementsTree[0] ) : getRelativePointFromPoint(nextStateData.scrollPosition); - + if (getRelativePointFromPoint(elementMetrics.frame.origin) <= (scrollPosition + getRelativeSizeFromSize(nextStateData.scrollContainer) + viewportOffset) && (getRelativePointFromPoint(elementMetrics.frame.origin) + getRelativeSizeFromSize(elementMetrics.frame.size)) >= (scrollPosition - viewportOffset)) { containerShadowNodeChildren->push_back(componentRegistry[componentRegistryItem.elementDataUniqueKey]); @@ -232,14 +231,14 @@ void SLContainerShadowNode::layout(LayoutContext layoutContext) { if (!props.uniqueIds.size()) { auto templateRegistryItem = transformTemplateComponent("ListEmptyComponentUniqueId", 1); containerShadowNodeChildren->push_back(componentRegistry["ListEmptyComponentUniqueId"]); - + adjustElement( { .x = 0, .y = scrollContentAboveOffset.max() + scrollContentBelowOffset.max() }, componentRegistry["ListEmptyComponentUniqueId"]); - + scrollContentBelowOffset.add(1, templateRegistryItem.size); } @@ -249,7 +248,7 @@ void SLContainerShadowNode::layout(LayoutContext layoutContext) { if (props.inverted) { transformTemplateComponent("ListHeaderComponentUniqueId", 1); containerShadowNodeChildren->push_back(componentRegistry["ListHeaderComponentUniqueId"]); - + adjustElement( { .x = 0, @@ -259,7 +258,7 @@ void SLContainerShadowNode::layout(LayoutContext layoutContext) { } else { transformTemplateComponent("ListFooterComponentUniqueId", 1); containerShadowNodeChildren->push_back(componentRegistry["ListFooterComponentUniqueId"]); - + adjustElement( { .x = 0, @@ -283,10 +282,9 @@ void SLContainerShadowNode::layout(LayoutContext layoutContext) { nextStateData.firstChildUniqueId = props.uniqueIds.front(); nextStateData.lastChildUniqueId = props.uniqueIds.back(); - nextStateData.scrollContainer = getLayoutMetrics().frame.size; - + if (props.horizontal) { - nextStateData.scrollContent.height = getLayoutMetrics().frame.size.height; + nextStateData.scrollContent.height = nextStateData.scrollContainer.height; nextStateData.scrollContent.width = ( scrollContentAboveOffset.max() + scrollContentBelowOffset.max() + @@ -294,7 +292,7 @@ void SLContainerShadowNode::layout(LayoutContext layoutContext) { ); nextStateData.scrollContentUpdated = true; } else if (!props.horizontal) { - nextStateData.scrollContent.width = getLayoutMetrics().frame.size.width; + nextStateData.scrollContent.width = nextStateData.scrollContainer.width; nextStateData.scrollContent.height = ( scrollContentAboveOffset.max() + scrollContentBelowOffset.max() + @@ -332,14 +330,14 @@ void SLContainerShadowNode::layout(LayoutContext layoutContext) { .visibleStartIndex = scrollContentBelowIndex, .visibleEndIndex = scrollContentAboveIndex, }); - + getConcreteEventEmitter().onScroll({ .contentSize = nextStateData.scrollContent, .contentOffset = nextStateData.scrollPosition, }); setStateData(std::move(nextStateData)); - + #ifndef RCT_DEBUG auto end = std::chrono::high_resolution_clock::now(); auto duration = std::chrono::duration_cast(end - start); @@ -378,7 +376,7 @@ LayoutMetrics SLContainerShadowNode::layoutElement(LayoutContext layoutContext, } elementShadowNodeLayoutable->layoutTree(layoutContext, layoutConstraints); - + return elementShadowNodeLayoutable->getLayoutMetrics(); } @@ -394,7 +392,7 @@ LayoutMetrics SLContainerShadowNode::adjustElement(Point origin, ShadowNode::Uns layoutMetrics.frame.origin.x = origin.x; } elementShadowNodeLayoutable->setLayoutMetrics(layoutMetrics); - + return layoutMetrics; } diff --git a/example/src/App.tsx b/example/src/App.tsx index 40bb933..9902a91 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -100,6 +100,7 @@ export default function App() { horizontal={IS_HORIZONTAL} initialScrollIndex={INITIAL_SCROLL_INDEX} numColumns={1} + contentContainerStyle={styles.list} /> ); @@ -111,6 +112,9 @@ const styles = StyleSheet.create({ backgroundColor: '#333333', paddingTop: 60, }, + list: { + flex: 1, + }, text: { color: '#333333', backgroundColor: '#1dd1a1', diff --git a/ios/Shadowlist.mm b/ios/Shadowlist.mm index 149ce10..10071cb 100644 --- a/ios/Shadowlist.mm +++ b/ios/Shadowlist.mm @@ -25,6 +25,7 @@ - (void)installJSIBindingsWithRuntime:(facebook::jsi::Runtime &)runtime { self->runtime_ = &runtime; SLRuntimeManager::getInstance().setRuntime(self->runtime_); + SLModuleJSI::install(runtime); SLModuleJSI::install(runtime, commitHook_); } diff --git a/package.json b/package.json index bbe569b..dfed738 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "shadowlist", - "version": "0.4.23", + "version": "0.4.24", "description": "60fps, high-performance list for React Native.", "source": "./src/index.tsx", "main": "./lib/commonjs/index.js", diff --git a/src/SLContainer.tsx b/src/SLContainer.tsx index 5965b00..7f44aff 100644 --- a/src/SLContainer.tsx +++ b/src/SLContainer.tsx @@ -96,11 +96,11 @@ const SLContainerWrapper = ( const styles = StyleSheet.create({ containerVertical: { - flex: 1, + height: '100%', flexDirection: 'column', }, containerHorizontal: { - flex: 1, + width: '100%', flexDirection: 'row', }, });