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(); } };