Skip to content

Commit a7527b4

Browse files
authored
feat:增加组件bbox计算方法 (#344)
* feat: 增加组件包围盒计算 * fix: 修复flex布局计算 * chore: 导出computeComponentBBox方法
1 parent a2505be commit a7527b4

6 files changed

Lines changed: 291 additions & 5 deletions

File tree

packages/f-engine/src/canvas/render/computeLayout.ts

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import Children from '../../children';
44
import { isNumber, isArray } from '@antv/util';
55
import getShapeAttrs from '../shape';
66
import { VNode } from '../vnode';
7-
import { Shape, FunctionComponent, getWorkTag } from '../workTags';
7+
import { Group } from '@antv/g-lite';
8+
import { createShape } from './createShape';
9+
import { Shape, FunctionComponent, getWorkTag, ClassComponent } from '../workTags';
810
import computeCSSLayout from './css-layout';
911

1012
export interface INode {
@@ -151,6 +153,19 @@ function renderJSXElement(element: JSX.Element, context, updater) {
151153
return renderJSXElement(newElement, context, updater);
152154
}
153155

156+
if (tag === ClassComponent) {
157+
// 创建组件实例
158+
const instance = new (type as any)(element.props, context, updater);
159+
160+
if (instance.WillMount) {
161+
instance.WillMount();
162+
}
163+
164+
const newElement = instance.render();
165+
166+
return renderJSXElement(newElement, context, updater);
167+
}
168+
154169
const { className, style: customStyle = {}, attrs, children: newChildren } = props;
155170

156171
const style = px2hd({
@@ -170,8 +185,18 @@ function renderJSXElement(element: JSX.Element, context, updater) {
170185
})
171186
: [];
172187

188+
const vNode = {
189+
key: undefined,
190+
tag,
191+
type,
192+
props,
193+
context,
194+
updater,
195+
};
196+
173197
return {
174198
type,
199+
vNode,
175200
className,
176201
children: nextChildren.filter(Boolean),
177202
style,
@@ -270,4 +295,55 @@ function fillComponentLayout(vNode: VNode) {
270295
});
271296
}
272297

273-
export { computeLayout, createNodeTree, computeCSSLayout, fillElementLayout, fillComponentLayout };
298+
function computeComponentBBox(component: Component | VNode, newChildren?: JSX.Element) {
299+
const { context, updater } = component;
300+
const { canvas } = context;
301+
const nodeTree = renderJSXElement(newChildren, context, updater);
302+
303+
if (nodeTree.style?.display === 'flex') {
304+
computeCSSLayout(nodeTree);
305+
fillElementLayout(nodeTree);
306+
}
307+
308+
const rootShape = new Group();
309+
traverseNodeTreeAndCreateShapes(nodeTree, rootShape, context);
310+
311+
canvas.getRoot().appendChild(rootShape);
312+
313+
const bbox = rootShape.getBBox();
314+
rootShape.remove();
315+
return bbox;
316+
}
317+
318+
function traverseNodeTreeAndCreateShapes(node, parentShape, context) {
319+
if (!node) return;
320+
321+
const { type, children, style: originStyle, vNode } = node;
322+
const { style: customStyle = {}, attrs = {} } = vNode;
323+
324+
const style = context.px2hd({
325+
...originStyle,
326+
...customStyle,
327+
...attrs,
328+
});
329+
330+
const shape = createShape(type, { style });
331+
332+
if (parentShape) {
333+
parentShape.appendChild(shape);
334+
}
335+
336+
Children.map(children, (child) => traverseNodeTreeAndCreateShapes(child, shape, context));
337+
338+
return shape;
339+
}
340+
341+
export {
342+
computeComponentBBox,
343+
computeLayout,
344+
renderJSXElement,
345+
createNodeTree,
346+
computeCSSLayout,
347+
fillElementLayout,
348+
fillComponentLayout,
349+
};

packages/f-engine/src/canvas/render/createShape.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ function createShape(type: string, props) {
9696
const ShapeClass = getTag(type);
9797
if (!ShapeClass) return null;
9898
// const result = checkCSSRule(type, originStyle);
99+
99100
const shape = new ShapeClass(props);
100101
// @ts-ignore
101102
shape.gesture = addEvent(shape, props);

packages/f-engine/src/canvas/render/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
computeCSSLayout,
1717
fillElementLayout,
1818
fillComponentLayout,
19+
computeComponentBBox,
1920
} from './computeLayout';
2021
import findClosestShapeNode from './findClosestShapeNode';
2122

@@ -76,6 +77,7 @@ function createVNode(parent: VNode, vNode: VNode) {
7677
const { canvas, context: parentContext, updater, animate: parentAnimate } = parent;
7778

7879
const { ref, type, props: originProps } = vNode;
80+
7981
const { animate, transformFrom, ...props } = originProps;
8082

8183
const tag = getWorkTag(type);
@@ -348,6 +350,7 @@ function renderChildren(
348350
// 计算 flex 布局
349351
const nodeTree = createNodeTree(parent);
350352
computeCSSLayout(nodeTree);
353+
351354
fillElementLayout(nodeTree);
352355
fillComponentLayout(parent);
353356

@@ -442,6 +445,7 @@ export {
442445
render,
443446
renderChildren,
444447
updateComponents,
448+
computeComponentBBox,
445449
computeLayout,
446450
destroyElement,
447451
getUpdateAnimation,

packages/f-engine/src/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { computeLayout } from './canvas/render';
1+
import { computeLayout, computeComponentBBox } from './canvas/render';
22
import { Renderer as CanvasRenderer } from '@antv/g-mobile-canvas';
33
import { parseColor } from '@antv/g-lite';
44
import * as Smooth from './shape/util/smooth';
@@ -14,8 +14,9 @@ export { default as createRef } from './createRef';
1414
export { default as createContext } from './createContext';
1515
export { default as Timeline, TimelineProps } from './timeline';
1616
export { default as Gesture } from './gesture';
17-
export { CanvasRenderer, computeLayout, Smooth, parseColor };
17+
export { CanvasRenderer, computeLayout, computeComponentBBox, Smooth, parseColor };
1818
export { default as isEqual } from './canvas/equal';
19+
1920
export { default as Player, PlayerProps } from './player';
2021

2122
// 导出 ts 类型
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
import { jsx, Canvas, Component } from '../../../src';
2+
import { computeComponentBBox } from '../../../src/canvas/render';
3+
import { createContext, delay } from '@antv/f-test-utils';
4+
5+
class View extends Component {
6+
willMount(): void {}
7+
render() {
8+
const { position } = this.props;
9+
return (
10+
<group
11+
style={{
12+
fill: 'red',
13+
}}
14+
>
15+
<circle
16+
style={{
17+
cx: position[0],
18+
cy: position[1],
19+
r: 20,
20+
fill: 'red',
21+
}}
22+
/>
23+
</group>
24+
);
25+
}
26+
}
27+
28+
class ViewWithDisplayGroup extends Component {
29+
willMount(): void {}
30+
render() {
31+
const { style, position = [20, 0] } = this.props;
32+
return (
33+
<group
34+
style={{
35+
fill: 'blue',
36+
display: 'flex',
37+
x: position[0],
38+
y: position[1],
39+
...style,
40+
alignItems: 'center',
41+
justifyContent: 'center',
42+
}}
43+
>
44+
<text
45+
style={{
46+
text: 'hello',
47+
textAlign: 'start',
48+
}}
49+
/>
50+
</group>
51+
);
52+
}
53+
}
54+
55+
class ViewText extends Component {
56+
willMount(): void {}
57+
render() {
58+
const { position } = this.props;
59+
return (
60+
<text
61+
style={{
62+
x: position[0],
63+
y: position[1],
64+
text: 'hello',
65+
}}
66+
/>
67+
);
68+
}
69+
}
70+
71+
class GuideGroup extends Component {
72+
bbox = [];
73+
74+
willMount(): void {
75+
const { children } = this.props;
76+
const { updater, context } = this;
77+
const childArray = Array.isArray(children) ? children : [children];
78+
childArray.map((child) => {
79+
const component = new child.type(child.props, context, updater);
80+
component.context = context;
81+
82+
const bbox = computeComponentBBox(this, component.render());
83+
this.bbox.push(bbox);
84+
});
85+
}
86+
render() {
87+
const { children } = this.props;
88+
return <group>{children}</group>;
89+
}
90+
}
91+
92+
describe('computeComponent', () => {
93+
it('组件bbox计算', async () => {
94+
const context = createContext();
95+
const ref = { current: null };
96+
const guideGroupRef = { current: null };
97+
const { props } = (
98+
<Canvas context={context}>
99+
<GuideGroup ref={guideGroupRef}>
100+
<ViewText position={[40, 40]} ref={ref} />
101+
<View position={[70, 70]} />
102+
</GuideGroup>
103+
</Canvas>
104+
);
105+
106+
const canvas = new Canvas(props);
107+
await canvas.render();
108+
109+
await delay(100);
110+
111+
const bbox = guideGroupRef.current.bbox;
112+
expect(bbox[0].x).toEqual(40);
113+
expect(bbox[0].y).toEqual(28);
114+
expect(bbox[0].width).toBeGreaterThan(0);
115+
expect(bbox[0].height).toBeGreaterThan(0);
116+
117+
expect(bbox[1].x).toEqual(50);
118+
expect(bbox[1].y).toEqual(50);
119+
expect(bbox[1].width).toEqual(40);
120+
expect(bbox[1].height).toEqual(40);
121+
});
122+
123+
it('View内的group带display样式', async () => {
124+
const context = createContext();
125+
const guideGroupRef = { current: null };
126+
const { props } = (
127+
<Canvas context={context}>
128+
<GuideGroup ref={guideGroupRef}>
129+
<ViewWithDisplayGroup />
130+
</GuideGroup>
131+
</Canvas>
132+
);
133+
134+
const canvas = new Canvas(props);
135+
await canvas.render();
136+
137+
await delay(100);
138+
139+
const bbox = guideGroupRef.current.bbox;
140+
expect(bbox[0].x).toEqual(20);
141+
expect(bbox[0].y).toEqual(0);
142+
expect(bbox[0].width).toBeGreaterThan(20);
143+
expect(bbox[0].height).toBeGreaterThan(10);
144+
});
145+
146+
it('View内的group带padding样式', async () => {
147+
const context = createContext();
148+
149+
const guideGroupRef = { current: null };
150+
const { props } = (
151+
<Canvas context={context}>
152+
<GuideGroup ref={guideGroupRef}>
153+
<ViewWithDisplayGroup position={[0, 0]} style={{ padding: [20, 20, 20, 20] }} />
154+
</GuideGroup>
155+
</Canvas>
156+
);
157+
158+
const canvas = new Canvas(props);
159+
await canvas.render();
160+
161+
await delay(100);
162+
163+
const bbox = guideGroupRef.current.bbox;
164+
165+
expect(bbox[0].x).toEqual(0);
166+
expect(bbox[0].y).toEqual(0);
167+
expect(bbox[0].width).toBeGreaterThan(40);
168+
expect(bbox[0].height).toBeGreaterThan(40);
169+
});
170+
});

packages/f-engine/test/g.test.tsx

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Canvas, CanvasEvent, Circle, convertToPath, Path, Rect } from '@antv/g-lite';
1+
import { Canvas, CanvasEvent, Circle, Text, convertToPath, Path, Rect, Group } from '@antv/g-lite';
22
import { Renderer as CanvasRenderer } from '@antv/g-mobile-canvas';
33
import '@antv/g-web-animations-api';
44
import { createContext, delay } from './util';
@@ -70,4 +70,38 @@ describe('G 的测试使用', () => {
7070

7171
await delay(1000);
7272
});
73+
74+
it('getBoxx', async () => {
75+
const circle = new Circle({
76+
style: {
77+
cx: 50,
78+
cy: 50,
79+
r: 20,
80+
fill: 'red',
81+
},
82+
});
83+
const circleBBox = circle.getBBox();
84+
85+
const group = new Group();
86+
const text = new Text({
87+
style: {
88+
x: 50,
89+
y: 50,
90+
text: 'test',
91+
},
92+
});
93+
canvas.appendChild(group);
94+
group.appendChild(text);
95+
const textBBox = text.getBBox();
96+
97+
expect(circleBBox).toBeDefined();
98+
expect(circleBBox.x).toBeCloseTo(30, 1);
99+
expect(circleBBox.y).toBeCloseTo(30, 1);
100+
expect(circleBBox.width).toBeCloseTo(40, 1);
101+
expect(circleBBox.height).toBeCloseTo(40, 1);
102+
103+
expect(textBBox).toBeDefined();
104+
expect(textBBox.x).toBeCloseTo(50, 1);
105+
expect(textBBox.y).toBeCloseTo(34, 1);
106+
});
73107
});

0 commit comments

Comments
 (0)