feat: initial implementation of isPointInFill

This commit is contained in:
Mikael Sand
2019-10-03 19:12:27 +03:00
parent b6ee11ad9e
commit 203e53b6ac
6 changed files with 285 additions and 3 deletions
@@ -0,0 +1,76 @@
/*
* Copyright (c) 2015-present, Horcrux.
* All rights reserved.
*
* This source code is licensed under the MIT-style license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.horcrux.svg;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.UiThreadUtil;
import javax.annotation.Nonnull;
class RNSVGRenderableManager extends ReactContextBaseJavaModule {
RNSVGRenderableManager(ReactApplicationContext reactContext) {
super(reactContext);
}
@Nonnull
@Override
public String getName() {
return "RNSVGRenderableManager";
}
private static void isPointInFill(final int tag, final float[] src, final Callback successCallback, final int attempt) {
UiThreadUtil.runOnUiThread(
new Runnable() {
@Override
public void run() {
RenderableView svg = RenderableViewManager.getRenderableViewByTag(tag);
if (svg == null) {
if (attempt < 1) {
RenderableViewManager.runWhenViewIsAvailable(tag, new Runnable() {
@Override
public void run() {
RenderableView svg = RenderableViewManager.getRenderableViewByTag(tag);
if (svg == null) { // Should never happen
successCallback.invoke(false);
return;
}
isPointInFill(tag, src, successCallback, attempt + 1);
}
});
} else {
successCallback.invoke(false);
}
return;
} else {
float scale = svg.mScale;
src[0] *= scale;
src[1] *= scale;
int i = svg.hitTest(src);
successCallback.invoke(i != -1);
}
}
}
);
}
@SuppressWarnings("unused")
@ReactMethod
public void isPointInFill(int tag, ReadableMap options, Callback successCallback) {
float x = (float)options.getDouble("x");
float y = (float)options.getDouble("y");
float[] src = new float[] { x, y };
isPointInFill(tag, src, successCallback, 0);
}
}
@@ -94,6 +94,12 @@ abstract public class RenderableView extends VirtualView {
private static final Pattern regex = Pattern.compile("[0-9.-]+");
@Override
public void setId(int id) {
super.setId(id);
RenderableViewManager.setRenderableView(id, this);
}
@ReactProp(name = "vectorEffect")
public void setVectorEffect(int vectorEffect) {
this.vectorEffect = vectorEffect;
@@ -10,6 +10,7 @@
package com.horcrux.svg;
import android.graphics.Matrix;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
@@ -41,7 +42,57 @@ import static com.facebook.react.uimanager.MatrixMathHelper.v3Cross;
import static com.facebook.react.uimanager.MatrixMathHelper.v3Dot;
import static com.facebook.react.uimanager.MatrixMathHelper.v3Length;
import static com.facebook.react.uimanager.MatrixMathHelper.v3Normalize;
import static com.facebook.react.uimanager.ViewProps.*;
import static com.facebook.react.uimanager.ViewProps.ALIGN_CONTENT;
import static com.facebook.react.uimanager.ViewProps.ALIGN_ITEMS;
import static com.facebook.react.uimanager.ViewProps.ALIGN_SELF;
import static com.facebook.react.uimanager.ViewProps.BORDER_BOTTOM_WIDTH;
import static com.facebook.react.uimanager.ViewProps.BORDER_END_WIDTH;
import static com.facebook.react.uimanager.ViewProps.BORDER_LEFT_WIDTH;
import static com.facebook.react.uimanager.ViewProps.BORDER_RIGHT_WIDTH;
import static com.facebook.react.uimanager.ViewProps.BORDER_START_WIDTH;
import static com.facebook.react.uimanager.ViewProps.BORDER_TOP_WIDTH;
import static com.facebook.react.uimanager.ViewProps.BORDER_WIDTH;
import static com.facebook.react.uimanager.ViewProps.BOTTOM;
import static com.facebook.react.uimanager.ViewProps.COLLAPSABLE;
import static com.facebook.react.uimanager.ViewProps.DISPLAY;
import static com.facebook.react.uimanager.ViewProps.END;
import static com.facebook.react.uimanager.ViewProps.FLEX;
import static com.facebook.react.uimanager.ViewProps.FLEX_BASIS;
import static com.facebook.react.uimanager.ViewProps.FLEX_DIRECTION;
import static com.facebook.react.uimanager.ViewProps.FLEX_GROW;
import static com.facebook.react.uimanager.ViewProps.FLEX_SHRINK;
import static com.facebook.react.uimanager.ViewProps.FLEX_WRAP;
import static com.facebook.react.uimanager.ViewProps.HEIGHT;
import static com.facebook.react.uimanager.ViewProps.JUSTIFY_CONTENT;
import static com.facebook.react.uimanager.ViewProps.LEFT;
import static com.facebook.react.uimanager.ViewProps.MARGIN;
import static com.facebook.react.uimanager.ViewProps.MARGIN_BOTTOM;
import static com.facebook.react.uimanager.ViewProps.MARGIN_END;
import static com.facebook.react.uimanager.ViewProps.MARGIN_HORIZONTAL;
import static com.facebook.react.uimanager.ViewProps.MARGIN_LEFT;
import static com.facebook.react.uimanager.ViewProps.MARGIN_RIGHT;
import static com.facebook.react.uimanager.ViewProps.MARGIN_START;
import static com.facebook.react.uimanager.ViewProps.MARGIN_TOP;
import static com.facebook.react.uimanager.ViewProps.MARGIN_VERTICAL;
import static com.facebook.react.uimanager.ViewProps.MAX_HEIGHT;
import static com.facebook.react.uimanager.ViewProps.MAX_WIDTH;
import static com.facebook.react.uimanager.ViewProps.MIN_HEIGHT;
import static com.facebook.react.uimanager.ViewProps.MIN_WIDTH;
import static com.facebook.react.uimanager.ViewProps.OVERFLOW;
import static com.facebook.react.uimanager.ViewProps.PADDING;
import static com.facebook.react.uimanager.ViewProps.PADDING_BOTTOM;
import static com.facebook.react.uimanager.ViewProps.PADDING_END;
import static com.facebook.react.uimanager.ViewProps.PADDING_HORIZONTAL;
import static com.facebook.react.uimanager.ViewProps.PADDING_LEFT;
import static com.facebook.react.uimanager.ViewProps.PADDING_RIGHT;
import static com.facebook.react.uimanager.ViewProps.PADDING_START;
import static com.facebook.react.uimanager.ViewProps.PADDING_TOP;
import static com.facebook.react.uimanager.ViewProps.PADDING_VERTICAL;
import static com.facebook.react.uimanager.ViewProps.POSITION;
import static com.facebook.react.uimanager.ViewProps.RIGHT;
import static com.facebook.react.uimanager.ViewProps.START;
import static com.facebook.react.uimanager.ViewProps.TOP;
import static com.facebook.react.uimanager.ViewProps.WIDTH;
import static com.horcrux.svg.RenderableView.CAP_ROUND;
import static com.horcrux.svg.RenderableView.FILL_RULE_NONZERO;
import static com.horcrux.svg.RenderableView.JOIN_ROUND;
@@ -1250,4 +1301,30 @@ class RenderableViewManager extends ViewGroupManager<VirtualView> {
throw new IllegalStateException("Unexpected type " + svgClass.toString());
}
}
private static final SparseArray<RenderableView> mTagToRenderableView = new SparseArray<>();
private static final SparseArray<Runnable> mTagToRunnable = new SparseArray<>();
static void setRenderableView(int tag, RenderableView svg) {
mTagToRenderableView.put(tag, svg);
Runnable task = mTagToRunnable.get(tag);
if (task != null) {
task.run();
mTagToRunnable.delete(tag);
}
}
static void runWhenViewIsAvailable(int tag, Runnable task) {
mTagToRunnable.put(tag, task);
}
static @Nullable RenderableView getRenderableViewByTag(int tag) {
return mTagToRenderableView.get(tag);
}
@Override
public void onDropViewInstance(@Nonnull VirtualView view) {
super.onDropViewInstance(view);
mTagToRenderableView.remove(view.getId());
}
}
@@ -54,7 +54,10 @@ public class SvgPackage implements ReactPackage {
@Nonnull
@Override
public List<NativeModule> createNativeModules(@Nonnull ReactApplicationContext reactContext) {
return Collections.<NativeModule>singletonList(new SvgViewModule(reactContext));
return Arrays.<NativeModule>asList(
new SvgViewModule(reactContext),
new RNSVGRenderableManager(reactContext)
);
}
@SuppressWarnings("unused")
+52
View File
@@ -6,6 +6,9 @@
* LICENSE file in the root directory of this source tree.
*/
#import <React/RCTBridge.h>
#import <React/RCTUIManager.h>
#import <React/RCTUIManagerUtils.h>
#import "RNSVGRenderableManager.h"
#import "RCTConvert+RNSVG.h"
@@ -34,4 +37,53 @@ RCT_EXPORT_VIEW_PROPERTY(strokeMiterlimit, CGFloat)
RCT_EXPORT_VIEW_PROPERTY(vectorEffect, int)
RCT_EXPORT_VIEW_PROPERTY(propList, NSArray<NSString *>)
- (void)isPointInFill:(nonnull NSNumber *)reactTag point:(CGPoint)point callback:(RCTResponseSenderBlock)callback attempt:(int)attempt {
[self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *,UIView *> *viewRegistry) {
__kindof UIView *view = viewRegistry[reactTag];
UIView *target;
if (!view) {
if (attempt < 1) {
void (^retryBlock)(void) = ^{
[self isPointInFill:reactTag point:point callback:callback attempt:(attempt + 1)];
};
RCTExecuteOnUIManagerQueue(retryBlock);
} else {
callback(@[[NSNumber numberWithBool:false]]);
}
return;
}
if ([view isKindOfClass:[RNSVGRenderable class]]) {
RNSVGRenderable *svg = view;
target = [svg hitTest:point withEvent:nil];
BOOL hit = target != nil;
callback(@[[NSNumber numberWithBool:hit]]);
} else {
RCTLogError(@"Invalid svg returned from registry, expecting RNSVGRenderable, got: %@", view);
callback(@[[NSNumber numberWithBool:false]]);
}
}];
}
RCT_EXPORT_METHOD(isPointInFill:(nonnull NSNumber *)reactTag options:(NSDictionary *)options callback:(RCTResponseSenderBlock)callback)
{
if (options == nil) {
RCTLogError(@"Invalid options given to isPointInFill, got: %@", options);
callback(@[[NSNumber numberWithBool:false]]);
return;
}
id xo = [options objectForKey:@"x"];
id yo = [options objectForKey:@"y"];
if (![xo isKindOfClass:NSNumber.class] ||
![yo isKindOfClass:NSNumber.class]) {
RCTLogError(@"Invalid x or y given to isPointInFill");
callback(@[[NSNumber numberWithBool:false]]);
return;
}
CGFloat x = (CGFloat)[xo floatValue];
CGFloat y = (CGFloat)[yo floatValue];
CGPoint point = CGPointMake(x, y);
[self isPointInFill:reactTag point:point callback:callback attempt:0];
}
@end
+69 -1
View File
@@ -1,13 +1,32 @@
import { Component } from 'react';
import SvgTouchableMixin from '../lib/SvgTouchableMixin';
import { NativeMethodsMixinStatic } from 'react-native';
import {
NativeModules,
findNodeHandle,
NativeMethodsMixinStatic,
} from 'react-native';
import { TransformProps } from '../lib/extract/types';
const RNSVGRenderableManager = NativeModules.RNSVGRenderableManager;
const { touchableGetInitialState } = SvgTouchableMixin;
const touchKeys = Object.keys(SvgTouchableMixin);
const touchVals = touchKeys.map(key => SvgTouchableMixin[key]);
const numTouchKeys = touchKeys.length;
interface SVGBoundingBoxOptions {
fill: boolean;
stroke: boolean;
markers: boolean;
clipped: boolean;
}
interface DOMPointInit {
x?: number;
y?: number;
z?: number;
w?: number;
}
export default class Shape<P> extends Component<P> {
[x: string]: unknown;
root: (Shape<P> & NativeMethodsMixinStatic) | null = null;
@@ -36,4 +55,53 @@ export default class Shape<P> extends Component<P> {
) => {
this.root && this.root.setNativeProps(props);
};
getBBox = (callback: () => void, options?: SVGBoundingBoxOptions) => {
if (!callback) {
return;
}
const handle = findNodeHandle(this.root as Component);
RNSVGRenderableManager.getBBox(handle, options, callback); // TODO
};
getCTM = (callback: () => void) => {
if (!callback) {
return;
}
const handle = findNodeHandle(this.root as Component);
RNSVGRenderableManager.getCTM(handle, callback); // TODO
};
getScreenCTM = (callback: () => void) => {
if (!callback) {
return;
}
const handle = findNodeHandle(this.root as Component);
RNSVGRenderableManager.getScreenCTM(handle, callback); // TODO
};
isPointInFill = (callback: () => void, options: DOMPointInit) => {
if (!callback) {
return;
}
const handle = findNodeHandle(this.root as Component);
RNSVGRenderableManager.isPointInFill(handle, options, callback);
};
isPointInStroke = (callback: () => void, options: DOMPointInit) => {
if (!callback) {
return;
}
const handle = findNodeHandle(this.root as Component);
RNSVGRenderableManager.isPointInStroke(handle, options, callback); // TODO
};
getTotalLength = (callback: () => void) => {
if (!callback) {
return;
}
const handle = findNodeHandle(this.root as Component);
RNSVGRenderableManager.getTotalLength(handle, callback); // TODO
};
getPointAtLength = (callback: () => void, options: Object) => {
if (!callback) {
return;
}
const handle = findNodeHandle(this.root as Component);
RNSVGRenderableManager.getPointAtLength(handle, options, callback); // TODO
};
}