diff --git a/packages/skia/src/dom/types/Drawings.ts b/packages/skia/src/dom/types/Drawings.ts
index 8dcfb9fc63..d6192d0407 100644
--- a/packages/skia/src/dom/types/Drawings.ts
+++ b/packages/skia/src/dom/types/Drawings.ts
@@ -32,6 +32,7 @@ import type {
export interface DrawingNodeProps extends GroupProps {
paint?: SkPaint;
+ zIndex?: number;
}
export type ImageProps = DrawingNodeProps &
diff --git a/packages/skia/src/renderer/__tests__/e2e/Drawings.spec.tsx b/packages/skia/src/renderer/__tests__/e2e/Drawings.spec.tsx
index ea71fdd534..fa78c5f15a 100644
--- a/packages/skia/src/renderer/__tests__/e2e/Drawings.spec.tsx
+++ b/packages/skia/src/renderer/__tests__/e2e/Drawings.spec.tsx
@@ -10,6 +10,7 @@ import {
Path,
Rect,
Image,
+ BlurMask,
} from "../../components";
import { images, importSkia, surface } from "../setup";
@@ -206,4 +207,39 @@ describe("Drawings", () => {
);
checkImage(image, "snapshots/drawings/image-default-props.png");
});
+
+ it("Should use the zIndex (1)", async () => {
+ const { width, height } = surface;
+ const r = width * 0.33;
+ const image = await surface.draw(
+
+
+
+
+
+
+ );
+ checkImage(image, "snapshots/drawings/zIndex.png");
+ });
+ it("Should use the zIndex (2)", async () => {
+ const { width, height } = surface;
+ const r = width * 0.33;
+ const image = await surface.draw(
+
+
+
+
+
+
+
+
+ );
+ checkImage(image, "snapshots/drawings/zIndex2.png");
+ });
});
diff --git a/packages/skia/src/sksg/Container.ts b/packages/skia/src/sksg/Container.ts
index 94a784dbbc..57718c46c3 100644
--- a/packages/skia/src/sksg/Container.ts
+++ b/packages/skia/src/sksg/Container.ts
@@ -15,7 +15,6 @@ const drawOnscreen = (Skia: Skia, nativeId: number, recording: Recording) => {
const rec = Skia.PictureRecorder();
const canvas = rec.beginRecording();
//const start = performance.now();
-
const ctx = createDrawingContext(Skia, recording.paintPool, canvas);
replay(ctx, recording.commands);
const picture = rec.finishRecordingAsPicture();
@@ -57,6 +56,7 @@ class StaticContainer extends Container {
const recorder = new Recorder();
visit(recorder, this.root);
this.recording = recorder.getRecording();
+ //printRecording(this.recording.commands);
const isOnScreen = this.nativeId !== -1;
if (isOnScreen) {
const rec = this.Skia.PictureRecorder();
diff --git a/packages/skia/src/sksg/Recorder/Core.ts b/packages/skia/src/sksg/Recorder/Core.ts
index 7a018eaeb1..3fca8e940c 100644
--- a/packages/skia/src/sksg/Recorder/Core.ts
+++ b/packages/skia/src/sksg/Recorder/Core.ts
@@ -25,90 +25,90 @@ import type {
DrawingNodeProps,
} from "../../dom/types";
-// export enum CommandType {
-// // Context
-// Group = "Group",
-// SavePaint = "SavePaint",
-// RestorePaint = "RestorePaint",
-// SaveCTM = "SaveCTM",
-// RestoreCTM = "RestoreCTM",
-// PushColorFilter = "PushColorFilter",
-// PushBlurMaskFilter = "PushBlurMaskFilter",
-// PushImageFilter = "PushImageFilter",
-// PushPathEffect = "PushPathEffect",
-// PushShader = "PushShader",
-// ComposeColorFilter = "ComposeColorFilter",
-// ComposeImageFilter = "ComposeImageFilter",
-// ComposePathEffect = "ComposePathEffect",
-// MaterializePaint = "MaterializePaint",
-// SaveBackdropFilter = "SaveBackdropFilter",
-// SaveLayer = "SaveLayer",
-// RestorePaintDeclaration = "RestorePaintDeclaration",
-// // Drawing
-// DrawBox = "DrawBox",
-// DrawImage = "DrawImage",
-// DrawCircle = "DrawCircle",
-// DrawPaint = "DrawPaint",
-// DrawPoints = "DrawPoints",
-// DrawPath = "DrawPath",
-// DrawRect = "DrawRect",
-// DrawRRect = "DrawRRect",
-// DrawOval = "DrawOval",
-// DrawLine = "DrawLine",
-// DrawPatch = "DrawPatch",
-// DrawVertices = "DrawVertices",
-// DrawDiffRect = "DrawDiffRect",
-// DrawText = "DrawText",
-// DrawTextPath = "DrawTextPath",
-// DrawTextBlob = "DrawTextBlob",
-// DrawGlyphs = "DrawGlyphs",
-// DrawPicture = "DrawPicture",
-// DrawImageSVG = "DrawImageSVG",
-// DrawParagraph = "DrawParagraph",
-// DrawAtlas = "DrawAtlas",
-// }
export enum CommandType {
// Context
- Group,
- SavePaint,
- RestorePaint,
- SaveCTM,
- RestoreCTM,
- PushColorFilter,
- PushBlurMaskFilter,
- PushImageFilter,
- PushPathEffect,
- PushShader,
- ComposeColorFilter,
- ComposeImageFilter,
- ComposePathEffect,
- MaterializePaint,
- SaveBackdropFilter,
- SaveLayer,
- RestorePaintDeclaration,
+ Group = "Group",
+ SavePaint = "SavePaint",
+ RestorePaint = "RestorePaint",
+ SaveCTM = "SaveCTM",
+ RestoreCTM = "RestoreCTM",
+ PushColorFilter = "PushColorFilter",
+ PushBlurMaskFilter = "PushBlurMaskFilter",
+ PushImageFilter = "PushImageFilter",
+ PushPathEffect = "PushPathEffect",
+ PushShader = "PushShader",
+ ComposeColorFilter = "ComposeColorFilter",
+ ComposeImageFilter = "ComposeImageFilter",
+ ComposePathEffect = "ComposePathEffect",
+ MaterializePaint = "MaterializePaint",
+ SaveBackdropFilter = "SaveBackdropFilter",
+ SaveLayer = "SaveLayer",
+ RestorePaintDeclaration = "RestorePaintDeclaration",
// Drawing
- DrawBox,
- DrawImage,
- DrawCircle,
- DrawPaint,
- DrawPoints,
- DrawPath,
- DrawRect,
- DrawRRect,
- DrawOval,
- DrawLine,
- DrawPatch,
- DrawVertices,
- DrawDiffRect,
- DrawText,
- DrawTextPath,
- DrawTextBlob,
- DrawGlyphs,
- DrawPicture,
- DrawImageSVG,
- DrawParagraph,
- DrawAtlas,
+ DrawBox = "DrawBox",
+ DrawImage = "DrawImage",
+ DrawCircle = "DrawCircle",
+ DrawPaint = "DrawPaint",
+ DrawPoints = "DrawPoints",
+ DrawPath = "DrawPath",
+ DrawRect = "DrawRect",
+ DrawRRect = "DrawRRect",
+ DrawOval = "DrawOval",
+ DrawLine = "DrawLine",
+ DrawPatch = "DrawPatch",
+ DrawVertices = "DrawVertices",
+ DrawDiffRect = "DrawDiffRect",
+ DrawText = "DrawText",
+ DrawTextPath = "DrawTextPath",
+ DrawTextBlob = "DrawTextBlob",
+ DrawGlyphs = "DrawGlyphs",
+ DrawPicture = "DrawPicture",
+ DrawImageSVG = "DrawImageSVG",
+ DrawParagraph = "DrawParagraph",
+ DrawAtlas = "DrawAtlas",
}
+// export enum CommandType {
+// // Context
+// Group,
+// SavePaint,
+// RestorePaint,
+// SaveCTM,
+// RestoreCTM,
+// PushColorFilter,
+// PushBlurMaskFilter,
+// PushImageFilter,
+// PushPathEffect,
+// PushShader,
+// ComposeColorFilter,
+// ComposeImageFilter,
+// ComposePathEffect,
+// MaterializePaint,
+// SaveBackdropFilter,
+// SaveLayer,
+// RestorePaintDeclaration,
+// // Drawing
+// DrawBox,
+// DrawImage,
+// DrawCircle,
+// DrawPaint,
+// DrawPoints,
+// DrawPath,
+// DrawRect,
+// DrawRRect,
+// DrawOval,
+// DrawLine,
+// DrawPatch,
+// DrawVertices,
+// DrawDiffRect,
+// DrawText,
+// DrawTextPath,
+// DrawTextBlob,
+// DrawGlyphs,
+// DrawPicture,
+// DrawImageSVG,
+// DrawParagraph,
+// DrawAtlas,
+// }
export type Command = {
type: T;
@@ -137,6 +137,8 @@ export const isCommand = (
interface GroupCommand extends Command {
children: Command[];
+ hasZIndex: boolean;
+ zIndex?: number | SharedValue;
}
export const isGroup = (command: Command): command is GroupCommand => {
diff --git a/packages/skia/src/sksg/Recorder/Player.ts b/packages/skia/src/sksg/Recorder/Player.ts
index caea4acd8b..39358f49fe 100644
--- a/packages/skia/src/sksg/Recorder/Player.ts
+++ b/packages/skia/src/sksg/Recorder/Player.ts
@@ -1,3 +1,8 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import type { SharedValue } from "react-native-reanimated";
+
+import { isSharedValue } from "../utils";
+
import {
drawCircle,
drawImage,
@@ -49,13 +54,56 @@ import {
} from "./Core";
import type { DrawingContext } from "./DrawingContext";
+type ZIndex = number | SharedValue | undefined;
+const materialize = (value: ZIndex) => {
+ "worklet";
+ if (value === undefined) {
+ return 0;
+ }
+ return isSharedValue(value) ? value.value : value;
+};
+
+function sortGroupCommands(commands: Command[]) {
+ "worklet";
+ // Find the leading state setup commands
+ const prefix: Command[] = [];
+ let i = 0;
+ while (i < commands.length && commands[i].zIndex === undefined) {
+ prefix.push(commands[i]);
+ i++;
+ }
+
+ // Find the trailing cleanup commands
+ const suffix: Command[] = [];
+ let j = commands.length - 1;
+ while (j >= 0 && commands[j].zIndex === undefined) {
+ suffix.unshift(commands[j]);
+ j--;
+ }
+
+ // Extract the drawable groups
+ const drawableGroups = commands.slice(i, j + 1);
+ // Sort the drawable groups by zIndex
+ const sortedGroups = drawableGroups.sort((a: any, b: any) => {
+ const zIndexA = materialize(a.zIndex);
+ const zIndexB = materialize(b.zIndex);
+ return zIndexA - zIndexB;
+ });
+
+ // Reconstruct the final command list
+ return [...prefix, ...sortedGroups, ...suffix];
+}
+
function play(ctx: DrawingContext, command: Command) {
"worklet";
if (isGroup(command)) {
- command.children.forEach((child) => play(ctx, child));
+ let sortedChildren = command.children;
+ if (command.hasZIndex) {
+ sortedChildren = sortGroupCommands(command.children);
+ }
+ sortedChildren.forEach((child) => play(ctx, child));
return;
}
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
materializeProps(command as any);
if (isCommand(command, CommandType.SaveBackdropFilter)) {
ctx.saveBackdropFilter();
diff --git a/packages/skia/src/sksg/Recorder/Recorder.ts b/packages/skia/src/sksg/Recorder/Recorder.ts
index 9c72ba0c15..610cf13839 100644
--- a/packages/skia/src/sksg/Recorder/Recorder.ts
+++ b/packages/skia/src/sksg/Recorder/Recorder.ts
@@ -92,9 +92,14 @@ export class Recorder {
this.cursors[this.cursors.length - 1].push(command);
}
- saveGroup() {
+ saveGroup(hasZIndex: boolean, zIndex?: number | SharedValue) {
const children: Command[] = [];
- this.add({ type: CommandType.Group, children });
+ this.add({
+ type: CommandType.Group,
+ children,
+ hasZIndex,
+ zIndex,
+ });
this.cursors.push(children);
}
diff --git a/packages/skia/src/sksg/Recorder/Visitor.ts b/packages/skia/src/sksg/Recorder/Visitor.ts
index e9eb3a829c..307978d5f2 100644
--- a/packages/skia/src/sksg/Recorder/Visitor.ts
+++ b/packages/skia/src/sksg/Recorder/Visitor.ts
@@ -196,10 +196,6 @@ const pushPaints = (recorder: Recorder, paints: Node[]) => {
};
const visitNode = (recorder: Recorder, node: Node) => {
- if (node.type === NodeType.Group) {
- recorder.saveGroup();
- }
- const { props } = node;
const {
colorFilters,
maskFilters,
@@ -209,6 +205,13 @@ const visitNode = (recorder: Recorder, node: Node) => {
pathEffects,
paints,
} = sortNodeChildren(node);
+ const hasZIndex = drawings.some(
+ (drawing: Node) => drawing.props.zIndex !== undefined
+ );
+ if (hasZIndex && node.type === NodeType.Group) {
+ recorder.saveGroup(hasZIndex);
+ }
+ const { props } = node;
const paint = processPaint(props);
const shouldPushPaint =
paint ||
@@ -309,8 +312,14 @@ const visitNode = (recorder: Recorder, node: Node) => {
recorder.drawAtlas(props);
break;
}
- drawings.forEach((drawing) => {
+ drawings.forEach((drawing: Node) => {
+ if (hasZIndex) {
+ recorder.saveGroup(false, drawing.props.zIndex ?? 0);
+ }
visitNode(recorder, drawing);
+ if (hasZIndex) {
+ recorder.restoreGroup();
+ }
});
if (shouldPushPaint) {
recorder.restorePaint();
@@ -318,7 +327,7 @@ const visitNode = (recorder: Recorder, node: Node) => {
if (shouldRestore) {
recorder.restoreCTM();
}
- if (node.type === NodeType.Group) {
+ if (hasZIndex && node.type === NodeType.Group) {
recorder.restoreGroup();
}
};