-
Notifications
You must be signed in to change notification settings - Fork 448
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
↕️ useCanvasSize() #380
↕️ useCanvasSize() #380
Changes from 4 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,4 +13,5 @@ export type Routes = { | |
BlendModes: undefined; | ||
Data: undefined; | ||
Picture: undefined; | ||
UseCanvas: undefined; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import { | ||
Canvas, | ||
Fill, | ||
Group, | ||
Rect, | ||
rect, | ||
useCanvasSize, | ||
useDerivedValue, | ||
} from "@shopify/react-native-skia"; | ||
import React, { useEffect, useRef } from "react"; | ||
import { View, Animated } from "react-native"; | ||
|
||
const MyComp = () => { | ||
const canvas = useCanvasSize(); | ||
const rct = useDerivedValue(() => { | ||
return rect(0, 0, canvas.current.width, canvas.current.height / 2); | ||
}, [canvas]); | ||
return ( | ||
<Group> | ||
<Fill color="magenta" /> | ||
<Rect color="cyan" rect={rct} /> | ||
</Group> | ||
); | ||
}; | ||
|
||
export const UseCanvas = () => { | ||
const height = useRef(new Animated.Value(0)); | ||
useEffect(() => { | ||
Animated.loop( | ||
Animated.timing(height.current, { | ||
toValue: 500, | ||
duration: 4000, | ||
useNativeDriver: false, | ||
}) | ||
).start(); | ||
}, []); | ||
return ( | ||
<View style={{ flex: 1 }}> | ||
<Canvas style={{ flex: 1 }}> | ||
<MyComp /> | ||
</Canvas> | ||
<Animated.View style={{ height: height.current }} /> | ||
</View> | ||
); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,8 +11,6 @@ import type { | |
RefObject, | ||
ReactNode, | ||
ComponentProps, | ||
Context, | ||
ReactElement, | ||
MutableRefObject, | ||
ForwardedRef, | ||
} from "react"; | ||
|
@@ -23,40 +21,21 @@ import { SkiaView, useDrawCallback } from "../views"; | |
import type { TouchHandler } from "../views"; | ||
import { Skia } from "../skia"; | ||
import type { FontMgr } from "../skia/FontMgr/FontMgr"; | ||
import { useValue } from "../values/hooks/useValue"; | ||
import type { SkiaReadonlyValue } from "../values/types"; | ||
|
||
import { debug as hostDebug, skHostConfig } from "./HostConfig"; | ||
// import { debugTree } from "./nodes"; | ||
import { vec } from "./processors"; | ||
import { Container } from "./nodes"; | ||
import { DependencyManager } from "./DependencyManager"; | ||
|
||
// useContextBridge() is taken from https://github.com/pmndrs/drei#usecontextbridge | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
export const useContextBridge = (...contexts: Context<any>[]) => { | ||
const values = | ||
// eslint-disable-next-line react-hooks/rules-of-hooks | ||
contexts.map((context) => useContext(context)); | ||
return useMemo( | ||
() => | ||
({ children }: { children: ReactNode }) => | ||
contexts.reduceRight( | ||
(acc, Context, i) => ( | ||
<Context.Provider value={values[i]} children={acc} /> | ||
), | ||
children | ||
) as ReactElement, | ||
[contexts, values] | ||
); | ||
}; | ||
|
||
interface CanvasContext { | ||
const CanvasContext = React.createContext<SkiaReadonlyValue<{ | ||
width: number; | ||
height: number; | ||
} | ||
}> | null>(null); | ||
|
||
const CanvasContext = React.createContext<CanvasContext | null>(null); | ||
|
||
export const useCanvas = () => { | ||
export const useCanvasSize = () => { | ||
const canvas = useContext(CanvasContext); | ||
if (!canvas) { | ||
throw new Error("Canvas context is not available"); | ||
|
@@ -93,6 +72,7 @@ const defaultFontMgr = Skia.FontMgr.RefDefault(); | |
|
||
export const Canvas = forwardRef<SkiaView, CanvasProps>( | ||
({ children, style, debug, mode, onTouch, fontMgr }, forwardedRef) => { | ||
const canvasCtx = useValue({ width: 0, height: 0 }); | ||
const innerRef = useCanvasRef(); | ||
const ref = useCombinedRefs(forwardedRef, innerRef); | ||
const [tick, setTick] = useState(0); | ||
|
@@ -103,21 +83,20 @@ export const Canvas = forwardRef<SkiaView, CanvasProps>( | |
[redraw, ref] | ||
); | ||
|
||
const canvasCtx = useRef({ width: 0, height: 0 }); | ||
const root = useMemo( | ||
() => skiaReconciler.createContainer(container, 0, false, null), | ||
[container] | ||
); | ||
// Render effect | ||
useEffect(() => { | ||
render( | ||
<CanvasContext.Provider value={canvasCtx.current}> | ||
<CanvasContext.Provider value={canvasCtx}> | ||
{children} | ||
</CanvasContext.Provider>, | ||
root, | ||
container | ||
); | ||
}, [children, root, redraw, container]); | ||
}, [children, root, redraw, container, canvasCtx]); | ||
|
||
// Draw callback | ||
const onDraw = useDrawCallback( | ||
|
@@ -127,6 +106,12 @@ export const Canvas = forwardRef<SkiaView, CanvasProps>( | |
if (onTouch) { | ||
onTouch(info.touches); | ||
} | ||
if ( | ||
width !== canvasCtx.current.width || | ||
height !== canvasCtx.current.height | ||
) { | ||
canvasCtx.current = { width, height }; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm thinking it might be possible to include more of the drawing context - I saw your comment about wanting to do so. Is there a specific reason for not doing so? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The idea was that useDerived() that depends on the canvasCtx value should only be run if we modify the width/height. But the expression is always re-evaluated on redraw? I will double check. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just checked and this does save some unnecessary JS evaluation. but of course let's keep it in mind (as well as offering a way to access the ref) |
||
const paint = Skia.Paint(); | ||
paint.setAntiAlias(true); | ||
const ctx = { | ||
|
@@ -140,7 +125,6 @@ export const Canvas = forwardRef<SkiaView, CanvasProps>( | |
center: vec(width / 2, height / 2), | ||
fontMgr: fontMgr ?? defaultFontMgr, | ||
}; | ||
canvasCtx.current = ctx; | ||
container.draw(ctx); | ||
}, | ||
[tick, onTouch] | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
export * from "./Canvas"; | ||
export * from "./components"; | ||
export * from "./nodes"; | ||
export * from "./useContextBridge"; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import React, { useMemo, useContext } from "react"; | ||
import type { ReactNode, Context, ReactElement } from "react"; | ||
|
||
// useContextBridge() is taken from https://github.com/pmndrs/drei#usecontextbridge | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
export const useContextBridge = (...contexts: Context<any>[]) => { | ||
const values = | ||
// eslint-disable-next-line react-hooks/rules-of-hooks | ||
contexts.map((context) => useContext(context)); | ||
return useMemo( | ||
() => | ||
({ children }: { children: ReactNode }) => | ||
contexts.reduceRight( | ||
(acc, Context, i) => ( | ||
<Context.Provider value={values[i]} children={acc} /> | ||
), | ||
children | ||
) as ReactElement, | ||
[contexts, values] | ||
); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't seem to be play well with hot reload, could this be improved?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does the reload problem materialise itself? It looks correct since the effect is dependent on the canvasCtx?