Skip to content

Commit cc43a95

Browse files
authored
Merge pull request #2100 from Shopify/rrect
Add support for nonuniform rounded rects
2 parents 1d57c8e + 61fc91a commit cc43a95

File tree

15 files changed

+247
-22
lines changed

15 files changed

+247
-22
lines changed
-1.78 KB
Binary file not shown.

docs/docs/shapes/polygons.md

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,37 @@ const RectDemo = () => {
5959
};
6060
```
6161

62-
![Rounded Rectangle](assets/polygons/rect.png)
62+
<img src={require("/static/img/rrect/uniform.png").default} width="256" height="256" />
63+
64+
### Using Custom Radii
65+
66+
You can set a different corner radius for each corner.
67+
68+
```tsx twoslash
69+
import { Canvas, RoundedRect } from "@shopify/react-native-skia";
70+
71+
const RectDemo = () => {
72+
const size = 256;
73+
const r = size * 0.2;
74+
const rrct = {
75+
rect: { x: 0, y: 0, width: size, height: size },
76+
topLeft: { x: 0, y: 0 },
77+
topRight: { x: r, y: r },
78+
bottomRight: { x: 0, y: 0 },
79+
bottomLeft: { x: r, y: r },
80+
};
81+
return (
82+
<Canvas style={{ width: size, height: size }}>
83+
<RoundedRect
84+
rect={rrct}
85+
color="lightblue"
86+
/>
87+
</Canvas>
88+
);
89+
};
90+
```
91+
92+
<img src={require("/static/img/rrect/nonuniform.png").default} width="256" height="256" />
6393

6494
## DiffRect
6595

docs/static/img/rrect/nonuniform.png

6.65 KB
Loading

docs/static/img/rrect/uniform.png

10.5 KB
Loading

package/cpp/api/JsiSkRRect.h

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include <memory>
44
#include <utility>
5+
#include <vector>
56

67
#include <jsi/jsi.h>
78

@@ -61,9 +62,35 @@ class JsiSkRRect : public JsiSkWrappingSharedPtrHostObject<SkRRect> {
6162
} else {
6263
auto rect =
6364
JsiSkRect::fromValue(runtime, object.getProperty(runtime, "rect"));
64-
auto rx = object.getProperty(runtime, "rx").asNumber();
65-
auto ry = object.getProperty(runtime, "ry").asNumber();
66-
return std::make_shared<SkRRect>(SkRRect::MakeRectXY(*rect, rx, ry));
65+
if (!object.getProperty(runtime, "rx").isUndefined()) {
66+
auto rx = object.getProperty(runtime, "rx").asNumber();
67+
auto ry = object.getProperty(runtime, "ry").asNumber();
68+
return std::make_shared<SkRRect>(SkRRect::MakeRectXY(*rect, rx, ry));
69+
} else if (object.getProperty(runtime, "topLeft").isObject() &&
70+
object.getProperty(runtime, "topRight").isObject() &&
71+
object.getProperty(runtime, "bottomRight").isObject() &&
72+
object.getProperty(runtime, "bottomLeft").isObject()) {
73+
std::vector<SkPoint> points;
74+
std::shared_ptr<SkPoint> topLeft = JsiSkPoint::fromValue(
75+
runtime, object.getProperty(runtime, "topLeft").asObject(runtime));
76+
std::shared_ptr<SkPoint> topRight = JsiSkPoint::fromValue(
77+
runtime, object.getProperty(runtime, "topRight").asObject(runtime));
78+
std::shared_ptr<SkPoint> bottomRight = JsiSkPoint::fromValue(
79+
runtime,
80+
object.getProperty(runtime, "bottomRight").asObject(runtime));
81+
std::shared_ptr<SkPoint> bottomLeft = JsiSkPoint::fromValue(
82+
runtime,
83+
object.getProperty(runtime, "bottomLeft").asObject(runtime));
84+
points.push_back(*topLeft.get());
85+
points.push_back(*topRight.get());
86+
points.push_back(*bottomRight.get());
87+
points.push_back(*bottomLeft.get());
88+
auto rrect = SkRRect::MakeEmpty();
89+
rrect.setRectRadii(*rect, points.data());
90+
return std::make_shared<SkRRect>(rrect);
91+
} else {
92+
throw jsi::JSError(runtime, "Invalid RRect object");
93+
}
6794
}
6895
}
6996

package/cpp/rnskia/dom/props/RRectProp.h

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "RectProp.h"
66

77
#include <memory>
8+
#include <vector>
89

910
#pragma clang diagnostic push
1011
#pragma clang diagnostic ignored "-Wdocumentation"
@@ -19,6 +20,10 @@ namespace RNSkia {
1920
static PropId PropNameRx = JsiPropId::get("rx");
2021
static PropId PropNameRy = JsiPropId::get("ry");
2122
static PropId PropNameR = JsiPropId::get("r");
23+
static PropId PropNameTopLeft = JsiPropId::get("topLeft");
24+
static PropId PropNameTopRight = JsiPropId::get("topRight");
25+
static PropId PropNameBottomRight = JsiPropId::get("bottomRight");
26+
static PropId PropNameBottomLeft = JsiPropId::get("bottomLeft");
2227

2328
/**
2429
Reads a rect from a given propety in the node. The name of the property is
@@ -64,6 +69,51 @@ class RRectProp : public DerivedProp<SkRRect> {
6469
width.getAsNumber(), height.getAsNumber()),
6570
rx.getAsNumber(), ry.getAsNumber()));
6671
}
72+
} else if (value.hasValue(PropNameRect) &&
73+
value.hasValue(PropNameTopLeft) &&
74+
value.hasValue(PropNameTopRight) &&
75+
value.hasValue(PropNameBottomRight) &&
76+
value.hasValue(PropNameBottomLeft)) {
77+
auto rect = value.getValue(PropNameRect);
78+
if (rect.hasValue(PropNameX) && rect.hasValue(PropNameY) &&
79+
rect.hasValue(PropNameWidth) && rect.hasValue(PropNameHeight)) {
80+
auto x = rect.getValue(PropNameX);
81+
auto y = rect.getValue(PropNameY);
82+
auto width = rect.getValue(PropNameWidth);
83+
auto height = rect.getValue(PropNameHeight);
84+
std::vector<SkPoint> points;
85+
points.reserve(4);
86+
auto topLeft = value.getValue(PropNameTopLeft);
87+
auto topLeftX = topLeft.getValue(PropNameX);
88+
auto topLeftY = topLeft.getValue(PropNameY);
89+
points.push_back(
90+
SkPoint::Make(topLeftX.getAsNumber(), topLeftY.getAsNumber()));
91+
92+
auto topRight = value.getValue(PropNameTopRight);
93+
auto topRightX = topRight.getValue(PropNameX);
94+
auto topRightY = topRight.getValue(PropNameY);
95+
points.push_back(SkPoint::Make(topRightX.getAsNumber(),
96+
topRightY.getAsNumber()));
97+
98+
auto bottomRight = value.getValue(PropNameBottomRight);
99+
auto bottomRightX = bottomRight.getValue(PropNameX);
100+
auto bottomRightY = bottomRight.getValue(PropNameY);
101+
points.push_back(SkPoint::Make(bottomRightX.getAsNumber(),
102+
bottomRightY.getAsNumber()));
103+
104+
auto bottomLeft = value.getValue(PropNameBottomLeft);
105+
auto bottomLeftX = bottomLeft.getValue(PropNameX);
106+
auto bottomLeftY = bottomLeft.getValue(PropNameY);
107+
points.push_back(SkPoint::Make(bottomLeftX.getAsNumber(),
108+
bottomLeftY.getAsNumber()));
109+
110+
auto rrect = std::make_shared<SkRRect>(SkRRect::MakeEmpty());
111+
rrect->setRectRadii(
112+
SkRect::MakeXYWH(x.getAsNumber(), y.getAsNumber(),
113+
width.getAsNumber(), height.getAsNumber()),
114+
points.data());
115+
return rrect;
116+
}
67117
}
68118
}
69119
}

package/src/dom/nodes/drawings/RRectNode.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import type { SkRRect } from "../../../skia/types";
1+
import type { InputRRect, SkRRect } from "../../../skia/types";
22
import type { DrawingContext, RoundedRectProps } from "../../types";
33
import { NodeType } from "../../types";
44
import { processRRect } from "../datatypes";
55
import { JsiDrawingNode } from "../DrawingNode";
66
import type { NodeContext } from "../Node";
77

8-
export class RRectNode extends JsiDrawingNode<RoundedRectProps, SkRRect> {
8+
export class RRectNode extends JsiDrawingNode<RoundedRectProps, InputRRect> {
99
rect?: SkRRect;
1010

1111
constructor(ctx: NodeContext, props: RoundedRectProps) {

package/src/dom/types/Common.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type {
44
BlendMode,
55
Color,
66
InputMatrix,
7+
InputRRect,
78
PaintStyle,
89
SkPaint,
910
SkPath,
@@ -48,7 +49,7 @@ export interface RRectCtor extends RectCtor {
4849
}
4950

5051
export type RectDef = RectCtor | { rect: SkRect };
51-
export type RRectDef = RRectCtor | { rect: SkRRect };
52+
export type RRectDef = RRectCtor | { rect: InputRRect };
5253

5354
export interface PointCircleDef {
5455
c?: Vector;

package/src/renderer/__tests__/e2e/Rect.spec.tsx

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import { surface } from "../setup";
1+
import React from "react";
2+
3+
import { checkImage, docPath } from "../../../__tests__/setup";
4+
import { Path, RoundedRect } from "../../components";
5+
import { importSkia, surface } from "../setup";
26

37
describe("Rects and rounded rects", () => {
48
it("The rounded rectangle radii should be scale to its maximum value", async () => {
@@ -26,4 +30,85 @@ describe("Rects and rounded rects", () => {
2630
});
2731
expect(result3).toEqual({ rx: 10, ry: 20 });
2832
});
33+
it("Should draw a rounded rect with uniform values (1)", async () => {
34+
const { width } = surface;
35+
const r = width * 0.2;
36+
const image = await surface.draw(
37+
<RoundedRect
38+
x={0}
39+
y={0}
40+
width={width}
41+
height={width}
42+
r={r}
43+
color="lightblue"
44+
/>
45+
);
46+
checkImage(image, docPath("rrect/uniform.png"));
47+
});
48+
it("Should draw a rounded rect with uniform values (2)", async () => {
49+
const { Skia } = importSkia();
50+
const { width } = surface;
51+
const r = width * 0.2;
52+
const rrct = Skia.RRectXY(Skia.XYWHRect(0, 0, width, width), r, r);
53+
const image = await surface.draw(
54+
<RoundedRect rect={rrct} color="lightblue" />
55+
);
56+
checkImage(image, docPath("rrect/uniform.png"));
57+
});
58+
it("Should draw a rounded rect with uniform values (3)", async () => {
59+
const { width } = surface;
60+
const r = width * 0.2;
61+
const rrct = {
62+
rect: { x: 0, y: 0, width, height: width },
63+
topLeft: { x: r, y: r },
64+
topRight: { x: r, y: r },
65+
bottomRight: { x: r, y: r },
66+
bottomLeft: { x: r, y: r },
67+
};
68+
const image = await surface.draw(
69+
<RoundedRect rect={rrct} color="lightblue" />
70+
);
71+
checkImage(image, docPath("rrect/uniform.png"));
72+
});
73+
it("Should draw a rounded rect with uniform values (4)", async () => {
74+
const { width } = surface;
75+
const r = width * 0.2;
76+
const { Skia } = importSkia();
77+
const rrct = Skia.RRectXY(Skia.XYWHRect(0, 0, width, width), r, r);
78+
const path = Skia.Path.Make();
79+
path.addRRect(rrct);
80+
const image = await surface.draw(<Path path={path} color="lightblue" />);
81+
checkImage(image, docPath("rrect/uniform.png"));
82+
});
83+
it("Should draw a rounded rect with non-uniform values (1)", async () => {
84+
const { width } = surface;
85+
const r = width * 0.2;
86+
const rrct = {
87+
rect: { x: 0, y: 0, width, height: width },
88+
topLeft: { x: 0, y: 0 },
89+
topRight: { x: r, y: r },
90+
bottomRight: { x: 0, y: 0 },
91+
bottomLeft: { x: r, y: r },
92+
};
93+
const image = await surface.draw(
94+
<RoundedRect rect={rrct} color="lightblue" />
95+
);
96+
checkImage(image, docPath("rrect/nonuniform.png"));
97+
});
98+
it("Should draw a rounded rect with non-uniform values (2)", async () => {
99+
const { Skia } = importSkia();
100+
const { width } = surface;
101+
const r = width * 0.2;
102+
const rrct = {
103+
rect: { x: 0, y: 0, width, height: width },
104+
topLeft: { x: 0, y: 0 },
105+
topRight: { x: r, y: r },
106+
bottomRight: { x: 0, y: 0 },
107+
bottomLeft: { x: r, y: r },
108+
};
109+
const path = Skia.Path.Make();
110+
path.addRRect(rrct);
111+
const image = await surface.draw(<Path path={path} color="lightblue" />);
112+
checkImage(image, docPath("rrect/nonuniform.png"));
113+
});
29114
});

package/src/skia/types/Canvas.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import type { SkPath } from "./Path";
55
import type { SkImage, MipmapMode, FilterMode, ImageInfo } from "./Image";
66
import type { SkSVG } from "./SVG";
77
import type { SkColor } from "./Color";
8-
import type { SkRRect } from "./RRect";
8+
import type { InputRRect } from "./RRect";
99
import type { BlendMode } from "./Paint/BlendMode";
1010
import type { SkPoint, PointMode } from "./Point";
1111
import type { InputMatrix } from "./Matrix";
@@ -278,7 +278,7 @@ export interface SkCanvas {
278278
* @param rrect
279279
* @param paint
280280
*/
281-
drawRRect(rrect: SkRRect, paint: SkPaint): void;
281+
drawRRect(rrect: InputRRect, paint: SkPaint): void;
282282

283283
/**
284284
* Draws RRect outer and inner using clip, Matrix, and Paint paint.
@@ -287,7 +287,7 @@ export interface SkCanvas {
287287
* @param inner
288288
* @param paint
289289
*/
290-
drawDRRect(outer: SkRRect, inner: SkRRect, paint: SkPaint): void;
290+
drawDRRect(outer: InputRRect, inner: InputRRect, paint: SkPaint): void;
291291

292292
/**
293293
* Draws an oval bounded by the given rectangle using the current clip, current matrix,
@@ -479,7 +479,7 @@ export interface SkCanvas {
479479
* @param op
480480
* @param doAntiAlias
481481
*/
482-
clipRRect(rrect: SkRRect, op: ClipOp, doAntiAlias: boolean): void;
482+
clipRRect(rrect: InputRRect, op: ClipOp, doAntiAlias: boolean): void;
483483

484484
/**
485485
* Replaces current matrix with m premultiplied with the existing matrix.

0 commit comments

Comments
 (0)