Skip to content

Commit

Permalink
Merge pull request #5 from azimgd/content-view
Browse files Browse the repository at this point in the history
Abstract internal Content Container from Scroll Container
  • Loading branch information
azimgd authored May 27, 2024
2 parents e7e2ecd + 3281871 commit a3b2e73
Show file tree
Hide file tree
Showing 38 changed files with 755 additions and 526 deletions.
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

0 comments on commit a3b2e73

Please sign in to comment.