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

WIP - Fabric ScrollView fixes #11

Draft
wants to merge 17 commits into
base: shwanton/fabric-core-fixes
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
006bb78
[fabric] Add support for native vertical inversion to enhanced scroll…
Sep 7, 2023
dfdfd54
[fabric] Add inverted property to scroll view
Sep 7, 2023
4849f8b
[fabric] Propagate component inverted property to native scroll view
Sep 7, 2023
b3cf76e
[fabric] Add RCTScrollContentComponentView Fabric Native View
shwanton Sep 26, 2023
a3ef096
[fabric] ScrollContentView should be mounted as document view of Scro…
shwanton Oct 13, 2023
9201887
[fabric] Subscribe to and dispatch NSScrollView scrolling notifications
Nov 9, 2023
10879f6
[fabric] Show autohiding scrollbars by default for RCTScrollViewCompo…
Nov 9, 2023
10da47b
[fabric] Support contentInset on RCTScrollViewComponentView
Nov 9, 2023
9282145
[fabric] Implement setContentOffset on Fabric scroll view to support …
Nov 10, 2023
c7e9757
[fabric] Implement zoomToRect on Fabric scroll view
Nov 10, 2023
f3a0906
[fabric] Implement flashScrollIndicators on Fabric scroll view
Nov 10, 2023
4080512
[fabric] Add content centering and offset bounds check to fabric scro…
Nov 14, 2023
7b7192e
[fabric] Adding a getter to layoutable shadow node to mark vertical a…
Mar 14, 2024
35c146f
[fabric] Support flipped views in relative layout metrics evaluation
Mar 14, 2024
600f994
[fabric] Add a custom shadow node to ScrollContentComponentView
Mar 15, 2024
460ac95
[fabric] Add view flipping propagation to ScrollView/ScrollContentVie…
Mar 15, 2024
409de4c
[fabric] Fix View content clipping to bounds toggling randomly on mount
Mar 21, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,18 @@
* @flow
*/

import type {
HostComponent,
PartialViewConfig,
} from '../../Renderer/shims/ReactNativeTypes';
import type {ViewProps as Props} from '../View/ViewPropTypes';
import type {HostComponent} from '../../Renderer/shims/ReactNativeTypes';
import type {ViewProps} from '../View/ViewPropTypes';

import * as NativeComponentRegistry from '../../NativeComponent/NativeComponentRegistry';
import codegenNativeComponent from '../../Utilities/codegenNativeComponent';

export const __INTERNAL_VIEW_CONFIG: PartialViewConfig = {
uiViewClassName: 'RCTScrollContentView',
bubblingEventTypes: {},
directEventTypes: {},
validAttributes: {},
};
type NativeProps = $ReadOnly<{|
...ViewProps,
inverted?: ?boolean,
|}>;

const ScrollContentViewNativeComponent: HostComponent<Props> =
NativeComponentRegistry.get<Props>(
'RCTScrollContentView',
() => __INTERNAL_VIEW_CONFIG,
);

export default ScrollContentViewNativeComponent;
export default (codegenNativeComponent<NativeProps>('ScrollContentView', {
paperComponentName: 'RCTScrollContentView',
excludedPlatforms: ['android'],
interfaceOnly: true,
}): HostComponent<NativeProps>);
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ Class<RCTComponentViewProtocol> RCTParagraphCls(void) __attribute__((used));
Class<RCTComponentViewProtocol> RCTPullToRefreshViewCls(void) __attribute__((used));
Class<RCTComponentViewProtocol> RCTSafeAreaViewCls(void) __attribute__((used));
Class<RCTComponentViewProtocol> RCTScrollViewCls(void) __attribute__((used));
#if TARGET_OS_OSX // [macOS
Class<RCTComponentViewProtocol> RCTScrollContentViewCls(void) __attribute__((used));
#endif // macOS]
Class<RCTComponentViewProtocol> RCTSwitchCls(void) __attribute__((used));
Class<RCTComponentViewProtocol> RCTTextInputCls(void) __attribute__((used));
Class<RCTComponentViewProtocol> RCTUnimplementedNativeViewCls(void) __attribute__((used));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
{"PullToRefreshView", RCTPullToRefreshViewCls},
{"SafeAreaView", RCTSafeAreaViewCls},
{"ScrollView", RCTScrollViewCls},
#if TARGET_OS_OSX // [macOS
{"ScrollContentView", RCTScrollContentViewCls},
#endif // macOS]
{"Switch", RCTSwitchCls},
{"TextInput", RCTTextInputCls},
{"UnimplementedNativeView", RCTUnimplementedNativeViewCls},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, assign) BOOL snapToEnd;
@property (nonatomic, copy) NSArray<NSNumber *> *snapToOffsets;

#if TARGET_OS_OSX // [macOS
@property (nonatomic, assign) BOOL inverted;

- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated;
- (void)zoomToRect:(CGRect)rect animated:(BOOL)animated;
- (void)flashScrollIndicators;
#endif // macOS]

/*
* Makes `setContentOffset:` method no-op when given `block` is executed.
* The block is being executed synchronously.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,60 @@ - (instancetype)initWithFrame:(CGRect)frame
[weakSelf setPrivateDelegate:delegate];
}];
[_delegateSplitter addDelegate:self];
#endif // [macOS]
#else // [macOS
self.hasHorizontalScroller = YES;
self.hasVerticalScroller = YES;
self.autohidesScrollers = YES;
#endif // macOS]
}

return self;
}

#if TARGET_OS_OSX // [macOS
- (void)setFrame:(NSRect)frame
{
// Preserving and revalidating `contentOffset`.
CGPoint originalOffset = self.contentOffset;

[super setFrame:frame];

UIEdgeInsets contentInset = self.contentInset;
CGSize contentSize = self.contentSize;

// If contentSize has not been measured yet we can't check bounds.
if (CGSizeEqualToSize(contentSize, CGSizeZero)) {
self.contentOffset = originalOffset;
} else {
CGSize boundsSize = self.bounds.size;
CGFloat xMaxOffset = contentSize.width - boundsSize.width + contentInset.right;
CGFloat yMaxOffset = contentSize.height - boundsSize.height + contentInset.bottom;
// Make sure offset doesn't exceed bounds. This can happen on screen rotation.
if ((originalOffset.x >= -contentInset.left) && (originalOffset.x <= xMaxOffset) &&
(originalOffset.y >= -contentInset.top) && (originalOffset.y <= yMaxOffset)) {
return;
}
self.contentOffset = CGPointMake(
MAX(-contentInset.left, MIN(xMaxOffset, originalOffset.x)),
MAX(-contentInset.top, MIN(yMaxOffset, originalOffset.y)));
}
}

- (BOOL)isFlipped
{
return !self.inverted;
}

- (NSSize)contentSize
{
if (!self.documentView) {
return [super contentSize];
}

return self.documentView.frame.size;
}
#endif // macos]

- (void)preserveContentOffsetWithBlock:(void (^)())block
{
if (!block) {
Expand All @@ -88,7 +136,11 @@ - (void)setContentOffset:(CGPoint)contentOffset
}

if (_centerContent && !CGSizeEqualToSize(self.contentSize, CGSizeZero)) {
#if !TARGET_OS_OSX // [macOS]
CGSize scrollViewSize = self.bounds.size;
#else // [macOS
CGSize scrollViewSize = self.contentView.bounds.size;
#endif // macOS]
if (self.contentSize.width <= scrollViewSize.width) {
contentOffset.x = -(scrollViewSize.width - self.contentSize.width) / 2.0;
}
Expand All @@ -97,11 +149,42 @@ - (void)setContentOffset:(CGPoint)contentOffset
}
}

#if !TARGET_OS_OSX // [macOS]
super.contentOffset = CGPointMake(
RCTSanitizeNaNValue(contentOffset.x, @"scrollView.contentOffset.x"),
RCTSanitizeNaNValue(contentOffset.y, @"scrollView.contentOffset.y"));
#else // [macOS
if (!NSEqualPoints(contentOffset, self.documentVisibleRect.origin)) {
[self.contentView scrollToPoint:contentOffset];
[self reflectScrolledClipView:self.contentView];
}
#endif // macOS]
}

#if TARGET_OS_OSX // [macOS
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated
{
if (animated) {
[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:0.3];
[[self.contentView animator] setBoundsOrigin:contentOffset];
[NSAnimationContext endGrouping];
} else {
self.contentOffset = contentOffset;
}
}

- (void)zoomToRect:(CGRect)rect animated:(BOOL)animated
{
[self magnifyToFitRect:rect];
}

- (void)flashScrollIndicators
{
[self flashScrollers];
}
#endif // macOS]

#if !TARGET_OS_OSX // [macOS]
- (BOOL)touchesShouldCancelInContentView:(RCTUIView *)view // [macOS]
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* 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.
*/

#import <React/RCTUIKit.h> // [macOS]

#import <React/RCTViewComponentView.h>

NS_ASSUME_NONNULL_BEGIN

@interface RCTScrollContentComponentView : RCTViewComponentView

@property (nonatomic, assign, getter=isInverted) BOOL inverted;

@end

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* 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.
*/

#import "RCTScrollContentComponentView.h"

#import <react/renderer/components/scrollview/ScrollContentViewComponentDescriptor.h>
#import <react/renderer/components/rncore/EventEmitters.h>
#import <react/renderer/components/rncore/Props.h>

#import "RCTFabricComponentsPlugins.h"

using namespace facebook::react;

@implementation RCTScrollContentComponentView

- (instancetype)init
{
if (self = [super init]) {
_props = std::make_shared<ScrollContentViewProps const>();
}

return self;
}

- (void)updateProps:(Props::Shared const&)props oldProps:(Props::Shared const&)oldProps {
const auto& newViewProps = *std::static_pointer_cast<ScrollContentViewProps const>(props);

[self setInverted:newViewProps.inverted];

[super updateProps:props oldProps:oldProps];
}

- (BOOL)isFlipped
{
return !self.inverted;
}

#pragma mark - RCTComponentViewProtocol

+ (ComponentDescriptorProvider)componentDescriptorProvider
{
return concreteComponentDescriptorProvider<ScrollContentViewComponentDescriptor>();
}

Class<RCTComponentViewProtocol> RCTScrollContentViewCls(void)
{
return RCTScrollContentComponentView.class;
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ NS_ASSUME_NONNULL_BEGIN
RCTGenericDelegateSplitter<id<UIScrollViewDelegate>> *scrollViewDelegateSplitter;
#endif // [macOS]

#if TARGET_OS_OSX // [macOS
@property (nonatomic, assign) UIEdgeInsets contentInset;
#endif // macOS]

@end

/*
Expand Down
Loading