Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Abstract internal Content Container from Scroll Container #5

Merged
merged 16 commits into from
May 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 12 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,27 +52,30 @@ import ShadowListContainer from 'shadowlist';
## API
| Prop | Type | Required | Description |
|----------------------------|---------------------------|----------|-------------------------------------------------|
| `data` | Array | Required | An array of data to be rendered in the list. |
| `contentContainerStyle` | ViewStyle | Optional | These styles will be applied to the scroll view content container which wraps all of the child views. |
| `ListHeaderComponent` | React component or null | Optional | A custom component to render at the top of the list. |
| `data` | Array | Required | An array of data to be rendered in the list. |
| `keyExtractor` | Function | Required | Used to extract a unique key for a given item at the specified index. |
| `contentContainerStyle` | ViewStyle | Optional | These styles will be applied to the scroll view content container which wraps all of the child views. |
| `ListHeaderComponent` | React component | Optional | A custom component to render at the top of the list. |
| `ListHeaderComponentStyle` | ViewStyle | Optional | Styling for internal View for `ListHeaderComponent` |
| `ListFooterComponent` | React component or null | Optional | A custom component to render at the bottom of the list. |
| `ListFooterComponent` | React component | Optional | A custom component to render at the bottom of the list. |
| `ListFooterComponentStyle` | ViewStyle | Optional | Styling for internal View for `ListFooterComponent` |
| `ListEmptyComponent` | React component or null | Optional | A custom component to render when the list is empty. |
| `ListEmptyComponent` | React component | Optional | A custom component to render when the list is empty. |
| `ListEmptyComponentStyle` | ViewStyle | Optional | Styling for internal View for `ListEmptyComponent` |
| `renderItem` | Function | Required | A function to render each item in the list. It receives an object with `item` and `index` properties. |
| `initialScrollIndex` | Number | Optional | The initial index of the item to scroll to when the list mounts. |
| `inverted` | Boolean | Optional | If true, the list will be rendered in an inverted order. |
| `horizontal` | Boolean | Optional | If true, renders items next to each other horizontally instead of stacked vertically. |
| `onBatchLayout` | `({ size: Int32 }) => void` | Optional | Called when a batch of layout calculations is complete. |
| `onEndReached` | `({ distanceFromEnd: Int32 }) => void` | Optional | Called when the end of the content is within `onEndReachedThreshold`. |
| `onEndReached` | Function | Optional | Called when the end of the content is within `onEndReachedThreshold`. |
| `onEndReachedThreshold` | Double | Optional | The threshold (in content length units) at which `onEndReached` is triggered. |
| `onStartReached` | Function | Optional | Called when the start of the content is within `onStartReachedThreshold`. |
| `onStartReachedThreshold` | Double | Optional | The threshold (in content length units) at which `onStartReached` is triggered. |


## Methods
| Method | Type | Description |
|-----------------|-------------------------------------|-----------------------------------------------------------|
| `scrollToIndex` | `({ index: number; animated: boolean }) => void` | Scrolls the list to the specified index. |
| `scrollToOffset`| `({ offset: number; animated: boolean }) => void` | Scrolls the list to the specified offset. |
| `scrollToIndex` | Function | Scrolls the list to the specified index. |
| `scrollToOffset`| Function | Scrolls the list to the specified offset. |

## Contributing

Expand All @@ -81,4 +84,3 @@ See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the
## License

MIT

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,19 @@ void ShadowListContainerEventEmitter::onVisibleChange(VisibleMetrics $event) con
});
}

void ShadowListContainerEventEmitter::onBatchLayout(BatchLayout $event) const {
dispatchEvent("batchLayout", [$event = std::move($event)](jsi::Runtime &runtime) {
void ShadowListContainerEventEmitter::onEndReached(EndReached $event) const {
dispatchEvent("endReached", [$event = std::move($event)](jsi::Runtime &runtime) {
auto $payload = jsi::Object(runtime);
$payload.setProperty(runtime, "size", $event.size);
$payload.setProperty(runtime, "distanceFromEnd", $event.distanceFromEnd);
return $payload;
});
}

void ShadowListContainerEventEmitter::onEndReached(EndReached $event) const {
dispatchEvent("endReached", [$event = std::move($event)](jsi::Runtime &runtime) {
void ShadowListContainerEventEmitter::onStartReached(StartReached $event) const {
dispatchEvent("startReached", [$event = std::move($event)](jsi::Runtime &runtime) {
auto $payload = jsi::Object(runtime);
$payload.setProperty(runtime, "distanceFromEnd", $event.distanceFromEnd);
$payload.setProperty(runtime, "distanceFromStart", $event.distanceFromStart);
return $payload;
});
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,17 @@ class ShadowListContainerEventEmitter : public ViewEventEmitter {
int end;
};

struct BatchLayout {
int size;
};

struct EndReached {
int distanceFromEnd;
};

struct StartReached {
int distanceFromStart;
};

void onVisibleChange(VisibleMetrics value) const;
void onBatchLayout(BatchLayout value) const;
void onEndReached(EndReached value) const;
void onStartReached(StartReached value) const;
};

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,9 @@ ShadowListContainerProps::ShadowListContainerProps(

inverted(convertRawProp(context, rawProps, "inverted", sourceProps.inverted, {false})),
horizontal(convertRawProp(context, rawProps, "horizontal", sourceProps.horizontal, {false})),
hasListHeaderComponent(convertRawProp(context, rawProps, "hasListHeaderComponent", sourceProps.hasListHeaderComponent, {false})),
hasListFooterComponent(convertRawProp(context, rawProps, "hasListFooterComponent", sourceProps.hasListFooterComponent, {false})),
initialScrollIndex(convertRawProp(context, rawProps, "initialScrollIndex", sourceProps.initialScrollIndex, {0})),
onEndReachedThreshold(convertRawProp(context, rawProps, "onEndReachedThreshold", sourceProps.onEndReachedThreshold, {0}))
onEndReachedThreshold(convertRawProp(context, rawProps, "onEndReachedThreshold", sourceProps.onEndReachedThreshold, {0})),
onStartReachedThreshold(convertRawProp(context, rawProps, "onStartReachedThreshold", sourceProps.onStartReachedThreshold, {0}))
{}

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,9 @@ class ShadowListContainerProps final : public ViewProps {

bool inverted{false};
bool horizontal{false};
bool hasListHeaderComponent{false};
bool hasListFooterComponent{false};
int initialScrollIndex{0};
double onEndReachedThreshold{0};
double onStartReachedThreshold{0};
};

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,70 +4,4 @@ namespace facebook::react {

extern const char ShadowListContainerComponentName[] = "ShadowListContainer";

/*
* Native layout function
*/
void ShadowListContainerShadowNode::layout(LayoutContext layoutContext) {
ensureUnsealed();
ConcreteShadowNode::layout(layoutContext);

auto &props = getConcreteProps();
auto state = getStateData();

calculateContainerMeasurements(
layoutContext,
props.horizontal,
props.inverted
);

if (scrollContainer_.size != state.scrollContainer) {
state.scrollContainer = scrollContainer_.size;
}

if (scrollContent_.size != state.scrollContent) {
state.scrollContent = scrollContent_.size;
state.scrollContentTree = scrollContentTree_;
}

if (props.initialScrollIndex && props.horizontal) {
state.scrollPosition = Point{state.calculateItemOffset(props.initialScrollIndex), 0};
} else if (props.initialScrollIndex) {
state.scrollPosition = Point{0, state.calculateItemOffset(props.initialScrollIndex)};
} else if (props.inverted && props.horizontal) {
state.scrollPosition = Point{scrollContent_.size.width - scrollContainer_.size.width, 0};
} else if (props.inverted) {
state.scrollPosition = Point{0, scrollContent_.size.height - scrollContainer_.size.height};
} else {
state.scrollPosition = Point{0, 0};
}

setStateData(std::move(state));

getConcreteEventEmitter().onBatchLayout({
.size = static_cast<int>(scrollContentTree_.size())
});
}

/*
* Measure visible container, and all childs aka list
*/
void ShadowListContainerShadowNode::calculateContainerMeasurements(LayoutContext layoutContext, bool horizontal, bool inverted) {
auto scrollContent = Rect{};
auto scrollContentTree = ShadowListFenwickTree(yogaNode_.getChildCount());

for (std::size_t index = 0; index < yogaNode_.getChildCount(); ++index) {
auto childYogaNode = yogaNode_.getChild(index);
auto childNodeMetrics = shadowNodeFromContext(childYogaNode).getLayoutMetrics();
scrollContent.unionInPlace(childNodeMetrics.frame);
scrollContentTree[index] = Scrollable::getScrollContentItemSize(childNodeMetrics.frame.size, horizontal);
}

scrollContent_ = scrollContent;
scrollContainer_ = getLayoutMetrics().frame;
scrollContentTree_ = scrollContentTree;
}

YogaLayoutableShadowNode& ShadowListContainerShadowNode::shadowNodeFromContext(YGNodeConstRef yogaNode) {
return dynamic_cast<YogaLayoutableShadowNode&>(*static_cast<ShadowNode*>(YGNodeGetContext(yogaNode)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
#include "ShadowListContainerEventEmitter.h"
#include "ShadowListContainerProps.h"
#include "ShadowListContainerState.h"
#include "ShadowListFenwickTree.hpp"
#include <react/renderer/graphics/Point.h>
#include <react/renderer/components/view/conversions.h>
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
Expand All @@ -26,24 +25,6 @@ class ShadowListContainerShadowNode final : public ConcreteViewShadowNode<

public:
using ConcreteViewShadowNode::ConcreteViewShadowNode;

void layout(LayoutContext layoutContext) override;

void calculateContainerMeasurements(LayoutContext layoutContext, bool horizontal, bool inverted);

private:

/*
* Measurements
*/
Rect scrollContainer_;
Rect scrollContent_;
ShadowListFenwickTree scrollContentTree_;

/*
* Caster
*/
static YogaLayoutableShadowNode& shadowNodeFromContext(YGNodeConstRef yogaNode);
};

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,81 +5,9 @@ namespace facebook::react {
ShadowListContainerState::ShadowListContainerState(
Point scrollPosition,
Size scrollContainer,
Size scrollContent,
ShadowListFenwickTree scrollContentTree) :
Size scrollContent) :

scrollPosition(scrollPosition),
scrollContainer(scrollContainer),
scrollContent(scrollContent),
scrollContentTree(scrollContentTree) {}

/*
* Measure layout and children metrics
*/
ShadowListContainerExtendedMetrics ShadowListContainerState::calculateExtendedMetrics(
Point scrollPosition,
bool horizontal,
bool inverted) const {

auto virtualizedOffset = Scrollable::getVirtualizedOffset();
auto scrollPositionOffset = Scrollable::getScrollPositionOffset(scrollPosition, horizontal);
auto scrollContentSize = Scrollable::getScrollContentSize(scrollContent, horizontal);
auto scrollContainerSize = Scrollable::getScrollContainerSize(scrollContainer, horizontal);

auto visibleStartPixels = std::max<float>(0.f, static_cast<double>(scrollPositionOffset));
auto visibleEndPixels = std::min<float>(scrollContentSize, scrollPositionOffset + scrollContainerSize);

int visibleStartIndex = scrollContentTree.lower_bound(visibleStartPixels);
visibleStartIndex = std::max(0, visibleStartIndex - virtualizedOffset);

int visibleEndIndex = scrollContentTree.lower_bound(visibleEndPixels);
visibleEndIndex = std::min(scrollContentTree.size(), size_t(visibleEndIndex + virtualizedOffset));

int blankTopStartIndex = 0;
int blankTopEndIndex = std::max(0, visibleStartIndex - 1);

auto blankTopStartPixels = 0.0;
auto blankTopEndPixels = scrollContentTree.sum(blankTopStartIndex, blankTopEndIndex);

int blankBottomStartIndex = std::min(size_t(visibleEndIndex + 1), scrollContentTree.size());
int blankBottomEndIndex = scrollContentTree.size();

auto blankBottomStartPixels = scrollContentTree.sum(blankBottomStartIndex, scrollContentTree.size());
auto blankBottomEndPixels = scrollContentTree.sum(0, scrollContentTree.size());

return ShadowListContainerExtendedMetrics{
visibleStartIndex,
visibleEndIndex,
visibleStartPixels,
visibleEndPixels,
blankTopStartIndex,
blankTopEndIndex,
blankTopStartPixels,
blankTopEndPixels,
blankBottomStartIndex,
blankBottomEndIndex,
blankBottomStartPixels,
blankBottomEndPixels,
};
}

/*
* Measure layout
*/
ShadowListContainerLayoutMetrics ShadowListContainerState::calculateLayoutMetrics() const {
auto height = scrollContentTree.sum(0, scrollContentTree.size());

return ShadowListContainerLayoutMetrics{
height
};
}

float ShadowListContainerState::calculateItemOffset(int index) const {
return scrollContentTree.sum(0, index);
}

int ShadowListContainerState::countTree() const {
return scrollContentTree.size();
}

scrollContent(scrollContent) {}
}
Loading
Loading