Skip to content
Open
4 changes: 4 additions & 0 deletions React/Views/NSView+React.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,5 +113,9 @@
* UIKit replacement
*/
@property (nonatomic, assign) BOOL clipsToBounds;
@property (nonatomic, assign) CATransform3D transform;

/** Populate the `layer` ivar when nil */
- (void)ensureLayerExists;

@end
28 changes: 25 additions & 3 deletions React/Views/NSView+React.m
Original file line number Diff line number Diff line change
Expand Up @@ -175,10 +175,10 @@ - (void)reactSetFrame:(CGRect)frame
return;
}

[self ensureLayerExists];
self.frame = frame;

// TODO: why position matters? It's only produce bugs
// OSX requires position and anchor point to rotate from center

// Ensure the anchorPoint is in the center.
self.layer.position = position;
self.layer.bounds = bounds;
self.layer.anchorPoint = anchor;
Expand Down Expand Up @@ -295,4 +295,26 @@ - (NSView *)reactAccessibilityElement
return self;
}

#pragma mark - Other

- (void)ensureLayerExists
{
if (!self.layer) {
self.wantsLayer = YES;
self.layer.delegate = (id<CALayerDelegate>)self;
}
}

- (CATransform3D)transform
{
return CATransform3DIdentity;
}

- (void)setTransform:(__unused CATransform3D)transform
{
// Do nothing by default.
// Native views must synthesize their own "transform" property,
// override "displayLayer:", and apply the transform there.
}

@end
14 changes: 5 additions & 9 deletions React/Views/RCTView.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,6 @@
*/
+ (NSEdgeInsets)contentInsetsForView:(NSView *)curView;

- (void)setBackgroundColor:(NSColor *)backgroundColor;

/**
* Layout direction of the view.
* This is inherited from UIView+React, but we override it here
Expand Down Expand Up @@ -114,19 +112,17 @@
@property (nonatomic, assign) CGFloat borderWidth;

/**
* Initial tranformation for a view which is not rendered yet
* Border styles.
*/
@property (nonatomic, assign) RCTBorderStyle borderStyle;


@property (nonatomic, assign) CATransform3D transform;
@property (nonatomic, assign) bool shouldBeTransformed;
@property (nonatomic, copy) NSColor *backgroundColor;

@property (nonatomic, copy) RCTDirectEventBlock onDragEnter;
@property (nonatomic, copy) RCTDirectEventBlock onDragLeave;
@property (nonatomic, copy) RCTDirectEventBlock onDrop;
@property (nonatomic, copy) RCTDirectEventBlock onContextMenuItemClick;
/**
* Border styles.
*/
@property (nonatomic, assign) RCTBorderStyle borderStyle;


@end
48 changes: 20 additions & 28 deletions React/Views/RCTView.m
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ - (instancetype)initWithFrame:(CGRect)frame
_borderBottomStartRadius = -1;
_borderBottomEndRadius = -1;
_borderStyle = RCTBorderStyleSolid;
_transform = CATransform3DIdentity;
self.clipsToBounds = NO;
}

Expand Down Expand Up @@ -192,6 +193,7 @@ - (void)setPointerEvents:(RCTPointerEvents)pointerEvents
- (void)setTransform:(CATransform3D)transform
{
_transform = transform;
[self setNeedsDisplay:YES];
}

- (NSView *)hitTest:(CGPoint)point
Expand Down Expand Up @@ -424,22 +426,11 @@ - (NSColor *)backgroundColor

- (void)setBackgroundColor:(NSColor *)backgroundColor
{
if ([_backgroundColor isEqual:backgroundColor]) {
return;
}
if (backgroundColor == nil) {
[self setWantsLayer:NO];
self.layer = NULL;
return;
}
if (![self wantsLayer] || self.layer == nil) {
[self setWantsLayer:YES];
self.layer.delegate = self;
}
[self.layer setBackgroundColor:[backgroundColor CGColor]];
[self.layer setNeedsDisplay];
[self setNeedsDisplay:YES];
_backgroundColor = backgroundColor;

[self ensureLayerExists];
self.layer.backgroundColor = backgroundColor.CGColor;
[self.layer setNeedsDisplay];
}

static CGFloat RCTDefaultIfNegativeTo(CGFloat defaultValue, CGFloat x) {
Expand Down Expand Up @@ -573,28 +564,29 @@ - (void)reactSetFrame:(CGRect)frame
[super reactSetFrame:frame];
if (!CGSizeEqualToSize(self.bounds.size, oldSize)) {
[self.layer setNeedsDisplay];
} else if (!CATransform3DIsIdentity(_transform)) {
[self applyTransform:self.layer];
}
}

- (void)ensureLayerExists
- (void)applyTransform:(CALayer *)layer
{
if (!self.layer) {
// Set `wantsLayer` first to create a "layer-backed view" instead of a "layer-hosting view".
self.wantsLayer = YES;

CALayer *layer = [CALayer layer];
layer.delegate = self;
self.layer = layer;
if (!CATransform3DEqualToTransform(_transform, layer.transform)) {
layer.transform = _transform;
// Enable edge antialiasing in perspective transforms
layer.edgeAntialiasingMask = !(_transform.m34 == 0.0f);
}
}

- (void)displayLayer:(CALayer *)layer
{
if (self.shouldBeTransformed) {
self.layer.transform = self.transform;
self.shouldBeTransformed = NO;
}

// Applying the transform here ensures it's not overridden by AppKit internals.
[self applyTransform:layer];

// Ensure the anchorPoint is in the center.
layer.position = (CGPoint){CGRectGetMidX(self.frame), CGRectGetMidY(self.frame)};
layer.anchorPoint = (CGPoint){0.5, 0.5};

if (CGSizeEqualToSize(layer.bounds.size, CGSizeZero)) {
return;
}
Expand Down
20 changes: 20 additions & 0 deletions React/Views/RCTViewManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,24 @@ RCT_REMAP_VIEW_PROPERTY(name, __custom__, type) \
RCT_REMAP_SHADOW_PROPERTY(name, __custom__, type) \
- (void)set_##name:(id)json forShadowView:(viewClass *)view

/**
* This macro maps a named property to an arbitrary key path in the view's layer.
*/
#define RCT_REMAP_LAYER_PROPERTY(name, keyPath, type) \
RCT_CUSTOM_VIEW_PROPERTY(name, type, RCTView) \
{ \
if (json) { \
[view ensureLayerExists]; \
view.layer.keyPath = [RCTConvert type:json]; \
} else { \
view.layer.keyPath = defaultView.layer.keyPath; \
} \
}

/**
* This handles the simple case, where JS and native property names match.
*/
#define RCT_EXPORT_LAYER_PROPERTY(name, type) \
RCT_REMAP_LAYER_PROPERTY(name, name, type)

@end
74 changes: 26 additions & 48 deletions React/Views/RCTViewManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -88,16 +88,6 @@ - (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(__unused NSDictio
return nil;
}

- (void)checkLayerExists:(NSView *)view
{
if (!view.layer) {
[view setWantsLayer:YES];
CALayer *viewLayer = [CALayer layer];
viewLayer.delegate = (id<CALayerDelegate>)view;
[view setLayer:viewLayer];
}
}

#pragma mark - View properties

#if TARGET_OS_TV
Expand All @@ -121,11 +111,12 @@ - (void)checkLayerExists:(NSView *)view
RCT_REMAP_VIEW_PROPERTY(testID, reactAccessibilityElement.accessibilityIdentifier, NSString)

RCT_EXPORT_VIEW_PROPERTY(backgroundColor, NSColor)
RCT_REMAP_VIEW_PROPERTY(backfaceVisibility, layer.doubleSided, css_backface_visibility_t)
RCT_REMAP_VIEW_PROPERTY(shadowColor, layer.shadowColor, CGColor)
RCT_REMAP_VIEW_PROPERTY(shadowOffset, layer.shadowOffset, CGSize)
RCT_REMAP_VIEW_PROPERTY(shadowOpacity, layer.shadowOpacity, float)
RCT_REMAP_VIEW_PROPERTY(shadowRadius, layer.shadowRadius, CGFloat)
RCT_REMAP_LAYER_PROPERTY(backfaceVisibility, doubleSided, css_backface_visibility_t)
RCT_EXPORT_LAYER_PROPERTY(opacity, float)
RCT_EXPORT_LAYER_PROPERTY(shadowColor, CGColor)
RCT_EXPORT_LAYER_PROPERTY(shadowOffset, CGSize)
RCT_EXPORT_LAYER_PROPERTY(shadowOpacity, float)
RCT_EXPORT_LAYER_PROPERTY(shadowRadius, CGFloat)
RCT_REMAP_VIEW_PROPERTY(toolTip, toolTip, NSString)
RCT_CUSTOM_VIEW_PROPERTY(overflow, YGOverflow, RCTView)
{
Expand All @@ -137,23 +128,14 @@ - (void)checkLayerExists:(NSView *)view
}
RCT_CUSTOM_VIEW_PROPERTY(shouldRasterizeIOS, BOOL, RCTView)
{
if (json) {
[view ensureLayerExists];
}
view.layer.shouldRasterize = json ? [RCTConvert BOOL:json] : defaultView.layer.shouldRasterize;
view.layer.rasterizationScale = view.layer.shouldRasterize ? [NSScreen mainScreen].backingScaleFactor : defaultView.layer.rasterizationScale;
}

RCT_CUSTOM_VIEW_PROPERTY(transform, CATransform3D, RCTView)
{
CATransform3D transform = json ? [RCTConvert CATransform3D:json] : defaultView.layer.transform;
if ([view respondsToSelector:@selector(shouldBeTransformed)] && !view.superview) {
view.shouldBeTransformed = YES;
view.transform = transform;
} else {
view.layer.transform = transform;
}

// TODO: Improve this by enabling edge antialiasing only for transforms with rotation or skewing
view.layer.edgeAntialiasingMask = !CATransform3DIsIdentity(transform);
}
RCT_EXPORT_VIEW_PROPERTY(transform, CATransform3D)

RCT_CUSTOM_VIEW_PROPERTY(draggedTypes, NSArray*<NSString *>, RCTView)
{
Expand All @@ -164,61 +146,57 @@ - (void)checkLayerExists:(NSView *)view
[view registerForDraggedTypes:defaultView.registeredDraggedTypes];
}
}

RCT_CUSTOM_VIEW_PROPERTY(opacity, float, RCTView)
{
if (json) {
[self checkLayerExists:view];
[view.layer setOpacity:[RCTConvert float:json]];
} else {
[view.layer setOpacity:1];
}
}


RCT_CUSTOM_VIEW_PROPERTY(pointerEvents, RCTPointerEvents, RCTView)
{
if ([view respondsToSelector:@selector(setPointerEvents:)]) {
view.pointerEvents = json ? [RCTConvert RCTPointerEvents:json] : defaultView.pointerEvents;
return;
}
}


RCT_CUSTOM_VIEW_PROPERTY(removeClippedSubviews, BOOL, RCTView)
{
if ([view respondsToSelector:@selector(setRemoveClippedSubviews:)]) {
view.removeClippedSubviews = json ? [RCTConvert BOOL:json] : defaultView.removeClippedSubviews;
}
}
RCT_CUSTOM_VIEW_PROPERTY(borderRadius, CGFloat, RCTView) {
RCT_CUSTOM_VIEW_PROPERTY(borderRadius, CGFloat, RCTView)
{
if (json) {
[view ensureLayerExists];
}
if ([view respondsToSelector:@selector(setBorderRadius:)]) {
[self checkLayerExists:view];
view.borderRadius = json ? [RCTConvert CGFloat:json] : defaultView.borderRadius;
} else {
view.layer.cornerRadius = json ? [RCTConvert CGFloat:json] : defaultView.layer.cornerRadius;
}
}
RCT_CUSTOM_VIEW_PROPERTY(borderColor, CGColor, RCTView)
{
if (json) {
[view ensureLayerExists];
}
if ([view respondsToSelector:@selector(setBorderColor:)]) {
[self checkLayerExists:view];
view.borderColor = json ? [RCTConvert CGColor:json] : defaultView.borderColor;
} else {
view.layer.borderColor = json ? [RCTConvert CGColor:json] : defaultView.layer.borderColor;
}
}
RCT_CUSTOM_VIEW_PROPERTY(borderWidth, float, RCTView)
{
if (json) {
[view ensureLayerExists];
}
if ([view respondsToSelector:@selector(setBorderWidth:)]) {
[self checkLayerExists:view];
view.borderWidth = json ? [RCTConvert CGFloat:json] : defaultView.borderWidth;
} else {
view.layer.borderWidth = json ? [RCTConvert CGFloat:json] : defaultView.layer.borderWidth;
}
}
RCT_CUSTOM_VIEW_PROPERTY(borderStyle, RCTBorderStyle, RCTView)
{
if (json) {
[view ensureLayerExists];
}
if ([view respondsToSelector:@selector(setBorderStyle:)]) {
view.borderStyle = json ? [RCTConvert RCTBorderStyle:json] : defaultView.borderStyle;
}
Expand Down Expand Up @@ -262,14 +240,14 @@ - (void)checkLayerExists:(NSView *)view
RCT_CUSTOM_VIEW_PROPERTY(border##SIDE##Width, float, RCTView) \
{ \
if ([view respondsToSelector:@selector(setBorder##SIDE##Width:)]) { \
[self checkLayerExists:view]; \
[view ensureLayerExists]; \
view.border##SIDE##Width = json ? [RCTConvert CGFloat:json] : defaultView.border##SIDE##Width; \
} \
} \
RCT_CUSTOM_VIEW_PROPERTY(border##SIDE##Color, NSColor, RCTView) \
{ \
if ([view respondsToSelector:@selector(setBorder##SIDE##Color:)]) { \
[self checkLayerExists:view]; \
[view ensureLayerExists]; \
view.border##SIDE##Color = json ? [RCTConvert CGColor:json] : defaultView.border##SIDE##Color; \
} \
}
Expand Down