Skip to content
8 changes: 8 additions & 0 deletions apps/example/src/Examples/API/OnSize.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ import { useDerivedValue, useSharedValue } from "react-native-reanimated";
export const OnSize = () => {
const size = useSharedValue({ width: 0, height: 0 });
const redRect = useDerivedValue(() => {
console.log(
"new size " +
size.value.width +
"x" +
size.value.height +
" @ " +
new Date()
);
return rect(0, 0, size.value.width, size.value.height);
});

Expand Down
36 changes: 34 additions & 2 deletions packages/skia/cpp/jsi/ViewProperty.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once

#include <functional>
#include <jsi/jsi.h>
#include <memory>
#include <string>
Expand All @@ -26,11 +27,42 @@ class ViewProperty {
}
}

bool isNull() { return std::holds_alternative<nullptr_t>(_value); }
template <typename PlatformContext>
ViewProperty(jsi::Runtime &runtime, const jsi::Value &value,
PlatformContext platformContext, size_t nativeId) {
// Set the onSize callback with all the necessary context
_value = std::function<void(int, int)>(
[&runtime, platformContext, nativeId](int width, int height) {
jsi::Object size(runtime);
auto pd = platformContext->getPixelDensity();
size.setProperty(runtime, "width", jsi::Value(width / pd));
size.setProperty(runtime, "height", jsi::Value(height / pd));

// Get the stored shared value from global
std::string globalKey =
"__onSize_" + std::to_string(static_cast<int>(nativeId));
auto globalProp =
runtime.global().getProperty(runtime, globalKey.c_str());
if (!globalProp.isUndefined()) {
globalProp.asObject(runtime).setProperty(runtime, "value", size);
}
});
}

bool isNull() { return std::holds_alternative<std::nullptr_t>(_value); }

sk_sp<SkPicture> getPicture() { return std::get<sk_sp<SkPicture>>(_value); }

std::variant<std::nullptr_t, std::function<void(int, int)>>
getOnSize() const {
if (std::holds_alternative<std::function<void(int, int)>>(_value)) {
return std::get<std::function<void(int, int)>>(_value);
}
return nullptr;
}

private:
std::variant<nullptr_t, sk_sp<SkPicture>> _value = nullptr;
std::variant<std::nullptr_t, sk_sp<SkPicture>, std::function<void(int, int)>>
_value = nullptr;
};
} // namespace RNJsi
38 changes: 14 additions & 24 deletions packages/skia/cpp/rnskia/RNSkJsiViewApi.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <vector>

#include "JsiHostObject.h"
#include "RNSkPictureView.h"
#include "RNSkPlatformContext.h"
#include "RNSkView.h"
#include "ViewProperty.h"
Expand Down Expand Up @@ -102,31 +103,20 @@ class RNSkJsiViewApi : public RNJsi::JsiHostObject,
ViewRegistry::getInstance().withViewInfo(
nativeId, [&](std::shared_ptr<RNSkViewInfo> info) {
auto name = arguments[1].asString(runtime).utf8(runtime);
if (name == "onSize" && isSharedValue(runtime, arguments[2])) {
jsi::Object size(runtime);
auto pd = _platformContext->getPixelDensity();
auto w = info->view != nullptr
? std::max(info->view->getScaledWidth(), 0)
: 0;
auto h = info->view != nullptr
? std::max(info->view->getScaledHeight(), 0)
: 0;

size.setProperty(runtime, "width", w / pd);
size.setProperty(runtime, "height", h / pd);
arguments[2].asObject(runtime).setProperty(runtime, "value", size);
} else {
info->props.insert_or_assign(
arguments[1].asString(runtime).utf8(runtime),
RNJsi::ViewProperty(runtime, arguments[2]));
if (info->props.find("onSize") == info->props.end()) {
info->props.insert_or_assign(
arguments[1].asString(runtime).utf8(runtime),
RNJsi::ViewProperty(runtime, arguments[2]));

// Now let's see if we have a view that we can update
if (info->view != nullptr) {
// Update view!
info->view->setNativeId(nativeId);
info->view->setJsiProperties(info->props);
info->props.clear();
}
"onSize", RNJsi::ViewProperty(runtime, arguments[2],
_platformContext, nativeId));
}
// Now let's see if we have a view that we can update
if (info->view != nullptr) {
// Update view!
info->view->setNativeId(nativeId);
info->view->setJsiProperties(info->props);
info->props.clear();
}
return nullptr; // Return type for template deduction
});
Expand Down
27 changes: 25 additions & 2 deletions packages/skia/cpp/rnskia/RNSkPictureView.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <mutex>
#include <string>
#include <unordered_map>
#include <variant>
#include <vector>

#include <jsi/jsi.h>
Expand Down Expand Up @@ -55,8 +56,25 @@ class RNSkPictureRenderer
_requestRedraw();
}

void setOnSize(
std::variant<std::nullptr_t, std::function<void(int, int)>> onSize) {
_onSize = onSize;
}

private:
bool performDraw(std::shared_ptr<RNSkCanvasProvider> canvasProvider) {
// Call onSize callback only if the size has changed
int currentWidth = canvasProvider->getWidth();
int currentHeight = canvasProvider->getHeight();

if (std::holds_alternative<std::function<void(int, int)>>(_onSize)) {
if (_lastWidth != currentWidth || _lastHeight != currentHeight) {
_lastWidth = currentWidth;
_lastHeight = currentHeight;
std::get<std::function<void(int, int)>>(_onSize)(currentWidth,
currentHeight);
}
}
return canvasProvider->renderToCanvas([=, this](SkCanvas *canvas) {
// Make sure to scale correctly
auto pd = _platformContext->getPixelDensity();
Expand All @@ -72,6 +90,9 @@ class RNSkPictureRenderer

std::shared_ptr<RNSkPlatformContext> _platformContext;
sk_sp<SkPicture> _picture;
std::variant<std::nullptr_t, std::function<void(int, int)>> _onSize = nullptr;
int _lastWidth = -1;
int _lastHeight = -1;
};

class RNSkPictureView : public RNSkView {
Expand All @@ -88,7 +109,7 @@ class RNSkPictureView : public RNSkView {

void setJsiProperties(
std::unordered_map<std::string, RNJsi::ViewProperty> &props) override {

// Base implementation - no onSize callback
for (auto &prop : props) {
if (prop.first == "picture") {
if (prop.second.isNull()) {
Expand All @@ -97,10 +118,12 @@ class RNSkPictureView : public RNSkView {
->setPicture(nullptr);
continue;
}

// Save picture
std::static_pointer_cast<RNSkPictureRenderer>(getRenderer())
->setPicture(prop.second.getPicture());
} else if (prop.first == "onSize") {
std::static_pointer_cast<RNSkPictureRenderer>(getRenderer())
->setOnSize(prop.second.getOnSize());
}
}
}
Expand Down
7 changes: 7 additions & 0 deletions packages/skia/cpp/rnskia/RNSkView.h
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,13 @@ class RNSkView : public std::enable_shared_from_this<RNSkView> {
virtual void setJsiProperties(
std::unordered_map<std::string, RNJsi::ViewProperty> &props) = 0;

virtual void
setJsiProperties(std::unordered_map<std::string, RNJsi::ViewProperty> &props,
std::function<jsi::Object(int, int)> onSize) {
// Default implementation just calls the base method, ignoring onSize
setJsiProperties(props);
}

void requestRedraw() {
if (!_redrawRequested) {
_redrawRequested = true;
Expand Down
36 changes: 33 additions & 3 deletions packages/skia/src/renderer/Canvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import type {
} from "react-native";
import { type SharedValue } from "react-native-reanimated";

import Rea from "../external/reanimated/ReanimatedProxy";
import { SkiaViewNativeId } from "../views/SkiaViewNativeId";
import SkiaPictureViewNativeComponent from "../specs/SkiaPictureViewNativeComponent";
import type { SkImage, SkRect, SkSize } from "../skia/types";
Expand Down Expand Up @@ -88,10 +89,39 @@ export const Canvas = ({
}, []);

// Root
const root = useMemo(
() => new SkiaSGRoot(Skia, nativeId, onSize),
[nativeId, onSize]
const root = useMemo(() => new SkiaSGRoot(Skia, nativeId), [nativeId]);

const updateSize = useCallback(
(value: SkSize) => {
if (onSize) {
onSize.value = value;
}
},
[onSize]
);
useEffect(() => {
if (onSize) {
const { runOnJS } = Rea;
const uiOnSize = Rea.makeMutable({ width: 0, height: 0 });
Rea.runOnUI(() => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
global[`__onSize_${nativeId}`] = uiOnSize;
uiOnSize.addListener(nativeId, (value) => {
runOnJS(updateSize)(value);
});
})();
return () => {
Rea.runOnUI(() => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
delete global[`__onSize_${nativeId}`];
uiOnSize.removeListener(nativeId);
})();
};
}
return undefined;
}, [onSize, nativeId, updateSize]);

// Render effects
useLayoutEffect(() => {
Expand Down
37 changes: 10 additions & 27 deletions packages/skia/src/sksg/Container.native.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import type { SharedValue } from "react-native-reanimated";

import Rea from "../external/reanimated/ReanimatedProxy";
import type { Skia, SkSize } from "../skia/types";
import type { Skia } from "../skia/types";
import { HAS_REANIMATED_3 } from "../external/reanimated/renderHelpers";
import type { JsiRecorder } from "../skia/types/Recorder";

Expand All @@ -12,15 +10,9 @@ import { visit } from "./Recorder/Visitor";
import "../skia/NativeSetup";
import "../views/api";

const nativeDrawOnscreen = (
nativeId: number,
recorder: JsiRecorder,
onSize?: SharedValue<SkSize>
) => {
const nativeDrawOnscreen = (nativeId: number, recorder: JsiRecorder) => {
"worklet";
if (onSize) {
SkiaViewApi.setJsiProperty(nativeId, "onSize", onSize);
}

//const start = performance.now();
const picture = recorder.play();
//const end = performance.now();
Expand All @@ -31,11 +23,7 @@ const nativeDrawOnscreen = (
class NativeReanimatedContainer extends Container {
private mapperId: number | null = null;

constructor(
Skia: Skia,
private nativeId: number,
private onSize?: SharedValue<SkSize>
) {
constructor(Skia: Skia, private nativeId: number) {
super(Skia);
}

Expand All @@ -51,28 +39,23 @@ class NativeReanimatedContainer extends Container {
visit(recorder, this.root);
const sharedValues = recorder.getSharedValues();
const sharedRecorder = recorder.getRecorder();
Rea.runOnUI((onSize?: SharedValue<SkSize>) => {
Rea.runOnUI(() => {
"worklet";
nativeDrawOnscreen(nativeId, sharedRecorder, onSize);
})(this.onSize);
nativeDrawOnscreen(nativeId, sharedRecorder);
})();
if (sharedValues.length > 0) {
const { onSize } = this;
this.mapperId = Rea.startMapper(() => {
"worklet";
sharedRecorder.applyUpdates(sharedValues);
nativeDrawOnscreen(nativeId, sharedRecorder, onSize);
nativeDrawOnscreen(nativeId, sharedRecorder);
}, sharedValues);
}
}
}

export const createContainer = (
Skia: Skia,
nativeId: number,
onSize?: SharedValue<SkSize>
) => {
export const createContainer = (Skia: Skia, nativeId: number) => {
if (HAS_REANIMATED_3 && nativeId !== -1) {
return new NativeReanimatedContainer(Skia, nativeId, onSize);
return new NativeReanimatedContainer(Skia, nativeId);
} else {
return new StaticContainer(Skia, nativeId);
}
Expand Down
10 changes: 2 additions & 8 deletions packages/skia/src/sksg/Container.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
import type { SharedValue } from "react-native-reanimated";

import type { Skia, SkSize } from "../skia/types";
import type { Skia } from "../skia/types";

import { StaticContainer } from "./StaticContainer";

export const createContainer = (
Skia: Skia,
nativeId: number,
_onSize?: SharedValue<SkSize>
) => {
export const createContainer = (Skia: Skia, nativeId: number) => {
return new StaticContainer(Skia, nativeId);
};
10 changes: 2 additions & 8 deletions packages/skia/src/sksg/Container.web.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import type { SharedValue } from "react-native-reanimated";

import Rea from "../external/reanimated/ReanimatedProxy";
import type { Skia, SkSize } from "../skia/types";
import type { Skia } from "../skia/types";
import { HAS_REANIMATED_3 } from "../external/reanimated/renderHelpers";

import type { Recording } from "./Recorder/Recorder";
Expand Down Expand Up @@ -64,11 +62,7 @@ class ReanimatedContainer extends Container {
}
}

export const createContainer = (
Skia: Skia,
nativeId: number,
_onSize?: SharedValue<SkSize>
) => {
export const createContainer = (Skia: Skia, nativeId: number) => {
if (HAS_REANIMATED_3 && nativeId !== -1) {
return new ReanimatedContainer(Skia, nativeId);
} else {
Expand Down
7 changes: 3 additions & 4 deletions packages/skia/src/sksg/Reconciler.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import type { ReactNode } from "react";
import type { OpaqueRoot } from "react-reconciler";
import type { SharedValue } from "react-native-reanimated";
import ReactReconciler from "react-reconciler";

import type { SkCanvas, Skia, SkSize } from "../skia/types";
import type { SkCanvas, Skia } from "../skia/types";
import { NodeType } from "../dom/types";

import { debug, sksgHostConfig } from "./HostConfig";
Expand All @@ -24,8 +23,8 @@ export class SkiaSGRoot {
private root: OpaqueRoot;
private container: Container;

constructor(public Skia: Skia, nativeId = -1, onSize?: SharedValue<SkSize>) {
this.container = createContainer(Skia, nativeId, onSize);
constructor(public Skia: Skia, nativeId = -1) {
this.container = createContainer(Skia, nativeId);
this.root = skiaReconciler.createContainer(
this.container,
0,
Expand Down
Loading