-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlayer.tsx
142 lines (127 loc) · 4.9 KB
/
layer.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
import React from "react";
import * as THREE from "three";
import { Mask, useMask } from "@react-three/drei";
import useSize from "@/lib/use-size";
import { LayerContext } from "@/lib/context";
import type { Position, SizeProps, StyleProps } from "@/lib/types";
import useChildren from "@/lib/use-children";
import useFlexbox, { calculateChildPosition } from "@/lib/use-flexbox";
import useStyle, { UseStyle } from "@/lib/use-style";
import useContextValue from "@/lib/use-context-value";
import useRoundedPlane from "@/lib/use-rounded-plane";
import useTexture from "@/lib/use-texture";
import { BackgroundImageRef } from "@/lib/types";
import LayerText from "@/lib/layer-text";
type Props = {
width?: SizeProps["width"];
height?: SizeProps["height"];
aspectRatio?: SizeProps["aspectRatio"];
children?: React.ReactNode;
style?: Partial<StyleProps>;
position?: Position;
text?: string;
};
const SHAPE_DETAIL = 32;
export type LayerRef = {
group: React.RefObject<THREE.Group>["current"];
style: UseStyle[0];
updateStyle: UseStyle[1];
};
function Layer(props: Props, ref: React.ForwardedRef<LayerRef>) {
const size = useSize(props.width, props.height, props.aspectRatio);
const [style, updateStyle] = useStyle(props.style);
const children = useChildren(props.children, style);
const flexbox = useFlexbox(children, size, style);
const contextValue = useContextValue(children, size);
const childrenPositions = React.useMemo(() => {
return children.map((child, index) => {
return calculateChildPosition(child, index, children, style, size);
});
}, [children, style, size]);
const groupRef = React.useRef<THREE.Group>(null);
React.useImperativeHandle(
ref,
() => {
return {
group: groupRef.current,
style,
updateStyle,
};
},
[style, updateStyle],
);
const renderOrder = React.useMemo(() => {
return contextValue.id + style.zIndex;
}, [contextValue.id, style.zIndex]);
const shape = useRoundedPlane(size, style);
const backgroundImageMeshRef = React.useRef<BackgroundImageRef>(null);
const textureSize = useTexture(style, size, backgroundImageMeshRef);
const mask = useMask(contextValue.id);
const childrenGroupRef = React.useRef<THREE.Group>(null);
React.useEffect(() => {
const childrenGroup = childrenGroupRef.current;
if (childrenGroup === null) return;
childrenGroup.traverse((child) => {
if (child instanceof THREE.Mesh && child.material instanceof THREE.Material) {
if (style.overflow === "visible") {
child.material.stencilWrite = false;
child.material.stencilRef = 0;
child.material.stencilFunc = THREE.AlwaysStencilFunc;
child.material.stencilFail = THREE.KeepStencilOp;
child.material.stencilZFail = THREE.KeepStencilOp;
child.material.stencilZPass = THREE.KeepStencilOp;
} else {
Object.assign(child.material, mask);
}
child.material.needsUpdate = true;
}
});
}, [children, mask, style.overflow]);
return (
<LayerContext.Provider value={contextValue}>
<group
ref={groupRef}
position-x={props.position?.[0]}
position-y={props.position?.[1]}
position-z={props.position?.[2]}
>
{/* mask */}
<Mask id={contextValue.id} renderOrder={renderOrder}>
<shapeGeometry args={[shape, SHAPE_DETAIL]} />
<meshBasicMaterial color="white" opacity={0} transparent={true} depthWrite={false} />
</Mask>
{/* backgroundColor */}
<mesh renderOrder={renderOrder + 1}>
<shapeGeometry args={[shape, SHAPE_DETAIL]} />
<meshBasicMaterial
color={style.backgroundColor === "transparent" ? "#ffffff" : style.backgroundColor}
depthWrite={false}
transparent={true}
opacity={style.backgroundColor === "transparent" ? 0 : style.opacity}
/>
</mesh>
{/* backgroundImage */}
{style.backgroundImage !== "none" && (
<mesh ref={backgroundImageMeshRef} renderOrder={renderOrder + 2}>
<planeGeometry args={[textureSize.width, textureSize.height]} />
<meshBasicMaterial depthWrite={false} transparent={true} opacity={style.opacity} {...mask} />
</mesh>
)}
{/* text */}
<LayerText textContent={props.text} renderOrder={renderOrder + 3} style={style} size={size} mask={mask} />
{/* children */}
<group ref={childrenGroupRef} position-x={flexbox.x} position-y={flexbox.y}>
{children.map((child, index) => {
const position = childrenPositions[index];
return (
<group key={index} position-x={position.x} position-y={position.y} renderOrder={renderOrder + 4}>
{child}
</group>
);
})}
</group>
</group>
</LayerContext.Provider>
);
}
export default React.forwardRef(Layer);