├── .npmrc ├── apps ├── test-app │ ├── .npmrc │ ├── android │ │ ├── app │ │ │ ├── src │ │ │ │ ├── main │ │ │ │ │ ├── res │ │ │ │ │ │ ├── values-night │ │ │ │ │ │ │ └── colors.xml │ │ │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ │ │ ├── ic_launcher.webp │ │ │ │ │ │ │ ├── ic_launcher_round.webp │ │ │ │ │ │ │ └── ic_launcher_foreground.webp │ │ │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ │ │ ├── ic_launcher.webp │ │ │ │ │ │ │ ├── ic_launcher_round.webp │ │ │ │ │ │ │ └── ic_launcher_foreground.webp │ │ │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ │ │ ├── ic_launcher.webp │ │ │ │ │ │ │ ├── ic_launcher_round.webp │ │ │ │ │ │ │ └── ic_launcher_foreground.webp │ │ │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ │ │ ├── ic_launcher.webp │ │ │ │ │ │ │ ├── ic_launcher_round.webp │ │ │ │ │ │ │ └── ic_launcher_foreground.webp │ │ │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ │ │ ├── ic_launcher.webp │ │ │ │ │ │ │ ├── ic_launcher_round.webp │ │ │ │ │ │ │ └── ic_launcher_foreground.webp │ │ │ │ │ │ ├── drawable-hdpi │ │ │ │ │ │ │ └── splashscreen_logo.png │ │ │ │ │ │ ├── drawable-mdpi │ │ │ │ │ │ │ └── splashscreen_logo.png │ │ │ │ │ │ ├── drawable-xhdpi │ │ │ │ │ │ │ └── splashscreen_logo.png │ │ │ │ │ │ ├── drawable-xxhdpi │ │ │ │ │ │ │ └── splashscreen_logo.png │ │ │ │ │ │ ├── drawable-xxxhdpi │ │ │ │ │ │ │ └── splashscreen_logo.png │ │ │ │ │ │ ├── values │ │ │ │ │ │ │ ├── colors.xml │ │ │ │ │ │ │ ├── strings.xml │ │ │ │ │ │ │ └── styles.xml │ │ │ │ │ │ ├── drawable │ │ │ │ │ │ │ ├── ic_launcher_background.xml │ │ │ │ │ │ │ └── rn_edit_text_material.xml │ │ │ │ │ │ └── mipmap-anydpi-v26 │ │ │ │ │ │ │ ├── ic_launcher.xml │ │ │ │ │ │ │ └── ic_launcher_round.xml │ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ │ └── java │ │ │ │ │ │ └── com │ │ │ │ │ │ └── anonymous │ │ │ │ │ │ └── testapp │ │ │ │ │ │ ├── MainApplication.kt │ │ │ │ │ │ └── MainActivity.kt │ │ │ │ └── debug │ │ │ │ │ └── AndroidManifest.xml │ │ │ ├── debug.keystore │ │ │ ├── proguard-rules.pro │ │ │ └── build.gradle │ │ ├── gradle │ │ │ └── wrapper │ │ │ │ ├── gradle-wrapper.jar │ │ │ │ └── gradle-wrapper.properties │ │ ├── .gitignore │ │ ├── build.gradle │ │ ├── settings.gradle │ │ ├── gradle.properties │ │ ├── gradlew.bat │ │ └── gradlew │ ├── assets │ │ ├── icon.png │ │ ├── favicon.png │ │ ├── splash-icon.png │ │ └── adaptive-icon.png │ ├── ios │ │ ├── ConfettiTest │ │ │ ├── Images.xcassets │ │ │ │ ├── Contents.json │ │ │ │ ├── SplashScreenLegacy.imageset │ │ │ │ │ ├── image.png │ │ │ │ │ ├── image@2x.png │ │ │ │ │ ├── image@3x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── AppIcon.appiconset │ │ │ │ │ ├── App-Icon-1024x1024@1x.png │ │ │ │ │ └── Contents.json │ │ │ │ └── SplashScreenBackground.colorset │ │ │ │ │ └── Contents.json │ │ │ ├── ConfettiTest-Bridging-Header.h │ │ │ ├── ConfettiTest.entitlements │ │ │ ├── Supporting │ │ │ │ └── Expo.plist │ │ │ ├── PrivacyInfo.xcprivacy │ │ │ ├── AppDelegate.swift │ │ │ ├── Info.plist │ │ │ └── SplashScreen.storyboard │ │ ├── Podfile.properties.json │ │ ├── ConfettiTest.xcworkspace │ │ │ └── contents.xcworkspacedata │ │ ├── .gitignore │ │ ├── .xcode.env │ │ ├── Podfile │ │ └── ConfettiTest.xcodeproj │ │ │ ├── xcshareddata │ │ │ └── xcschemes │ │ │ │ └── ConfettiTest.xcscheme │ │ │ └── project.pbxproj │ ├── babel.config.js │ ├── index.ts │ ├── tsconfig.json │ ├── .gitignore │ ├── metro.config.js │ ├── app.json │ ├── package.json │ └── App.tsx └── test-web-app │ ├── README.md │ ├── src │ ├── vite-env.d.ts │ ├── main.tsx │ ├── index.css │ └── App.tsx │ ├── tsconfig.json │ ├── vite.config.ts │ ├── index.html │ ├── .gitignore │ ├── tsconfig.node.json │ ├── tsconfig.app.json │ └── package.json ├── pnpm-workspace.yaml ├── packages └── typegpu-confetti │ ├── src │ ├── react │ │ ├── index.ts │ │ ├── ConfettiProvider.tsx │ │ └── Confetti.tsx │ ├── context.ts │ ├── react-native │ │ ├── index.ts │ │ ├── utils.ts │ │ ├── ConfettiProvider.tsx │ │ └── Confetti.tsx │ ├── index.ts │ ├── types.ts │ ├── defaults.ts │ ├── utils.ts │ └── schemas.ts │ ├── tsdown.config.ts │ ├── tsconfig.json │ ├── package.json │ ├── prepack.mjs │ └── README.md ├── package.json ├── .gitignore ├── .zed └── settings.json ├── tsconfig.base.json ├── biome.json └── README.md /.npmrc: -------------------------------------------------------------------------------- 1 | public-hoist-pattern[]=* -------------------------------------------------------------------------------- /apps/test-app/.npmrc: -------------------------------------------------------------------------------- 1 | node-linker=hoisted 2 | -------------------------------------------------------------------------------- /apps/test-web-app/README.md: -------------------------------------------------------------------------------- 1 | # typegpu-confetti test web app -------------------------------------------------------------------------------- /apps/test-app/android/app/src/main/res/values-night/colors.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/test-web-app/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /apps/test-app/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/software-mansion-labs/typegpu-confetti/HEAD/apps/test-app/assets/icon.png -------------------------------------------------------------------------------- /apps/test-app/assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/software-mansion-labs/typegpu-confetti/HEAD/apps/test-app/assets/favicon.png -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - packages/* 3 | - apps/* 4 | onlyBuiltDependencies: 5 | - "@biomejs/biome" 6 | - esbuild 7 | -------------------------------------------------------------------------------- /apps/test-app/ios/ConfettiTest/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "version": 1, 4 | "author": "expo" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /apps/test-app/assets/splash-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/software-mansion-labs/typegpu-confetti/HEAD/apps/test-app/assets/splash-icon.png -------------------------------------------------------------------------------- /apps/test-app/assets/adaptive-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/software-mansion-labs/typegpu-confetti/HEAD/apps/test-app/assets/adaptive-icon.png -------------------------------------------------------------------------------- /apps/test-app/android/app/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/software-mansion-labs/typegpu-confetti/HEAD/apps/test-app/android/app/debug.keystore -------------------------------------------------------------------------------- /apps/test-app/ios/Podfile.properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo.jsEngine": "hermes", 3 | "EX_DEV_CLIENT_NETWORK_INSPECTOR": "true", 4 | "newArchEnabled": "true" 5 | } 6 | -------------------------------------------------------------------------------- /apps/test-app/ios/ConfettiTest/ConfettiTest-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | -------------------------------------------------------------------------------- /apps/test-web-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { "path": "./tsconfig.app.json" }, 5 | { "path": "./tsconfig.node.json" } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /apps/test-app/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/software-mansion-labs/typegpu-confetti/HEAD/apps/test-app/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /packages/typegpu-confetti/src/react/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Confetti } from './Confetti'; 2 | 3 | export { 4 | ConfettiProvider, 5 | useConfetti, 6 | } from './ConfettiProvider'; 7 | -------------------------------------------------------------------------------- /apps/test-app/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = (api) => { 2 | api.cache(true); 3 | return { 4 | presets: ['babel-preset-expo'], 5 | plugins: ['unplugin-typegpu/babel'], 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /packages/typegpu-confetti/src/context.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | import type { TgpuRoot } from 'typegpu'; 3 | 4 | export const RootContext = createContext(null); 5 | -------------------------------------------------------------------------------- /packages/typegpu-confetti/src/react-native/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Confetti } from './Confetti'; 2 | 3 | export { 4 | ConfettiProvider, 5 | useConfetti, 6 | } from './ConfettiProvider'; 7 | -------------------------------------------------------------------------------- /apps/test-app/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/software-mansion-labs/typegpu-confetti/HEAD/apps/test-app/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /apps/test-app/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/software-mansion-labs/typegpu-confetti/HEAD/apps/test-app/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /apps/test-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/software-mansion-labs/typegpu-confetti/HEAD/apps/test-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /apps/test-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/software-mansion-labs/typegpu-confetti/HEAD/apps/test-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /apps/test-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/software-mansion-labs/typegpu-confetti/HEAD/apps/test-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /apps/test-app/android/app/src/main/res/drawable-hdpi/splashscreen_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/software-mansion-labs/typegpu-confetti/HEAD/apps/test-app/android/app/src/main/res/drawable-hdpi/splashscreen_logo.png -------------------------------------------------------------------------------- /apps/test-app/android/app/src/main/res/drawable-mdpi/splashscreen_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/software-mansion-labs/typegpu-confetti/HEAD/apps/test-app/android/app/src/main/res/drawable-mdpi/splashscreen_logo.png -------------------------------------------------------------------------------- /apps/test-app/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/software-mansion-labs/typegpu-confetti/HEAD/apps/test-app/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /apps/test-app/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/software-mansion-labs/typegpu-confetti/HEAD/apps/test-app/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /apps/test-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/software-mansion-labs/typegpu-confetti/HEAD/apps/test-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /apps/test-app/android/app/src/main/res/drawable-xhdpi/splashscreen_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/software-mansion-labs/typegpu-confetti/HEAD/apps/test-app/android/app/src/main/res/drawable-xhdpi/splashscreen_logo.png -------------------------------------------------------------------------------- /apps/test-app/android/app/src/main/res/drawable-xxhdpi/splashscreen_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/software-mansion-labs/typegpu-confetti/HEAD/apps/test-app/android/app/src/main/res/drawable-xxhdpi/splashscreen_logo.png -------------------------------------------------------------------------------- /apps/test-app/android/app/src/main/res/drawable-xxxhdpi/splashscreen_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/software-mansion-labs/typegpu-confetti/HEAD/apps/test-app/android/app/src/main/res/drawable-xxxhdpi/splashscreen_logo.png -------------------------------------------------------------------------------- /apps/test-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/software-mansion-labs/typegpu-confetti/HEAD/apps/test-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /apps/test-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/software-mansion-labs/typegpu-confetti/HEAD/apps/test-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /apps/test-app/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/software-mansion-labs/typegpu-confetti/HEAD/apps/test-app/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /apps/test-app/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/software-mansion-labs/typegpu-confetti/HEAD/apps/test-app/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /apps/test-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/software-mansion-labs/typegpu-confetti/HEAD/apps/test-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /apps/test-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/software-mansion-labs/typegpu-confetti/HEAD/apps/test-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /apps/test-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/software-mansion-labs/typegpu-confetti/HEAD/apps/test-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /apps/test-app/android/.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Android/IntelliJ 6 | # 7 | build/ 8 | .idea 9 | .gradle 10 | local.properties 11 | *.iml 12 | *.hprof 13 | .cxx/ 14 | 15 | # Bundle artifacts 16 | *.jsbundle 17 | -------------------------------------------------------------------------------- /apps/test-app/ios/ConfettiTest/Images.xcassets/SplashScreenLegacy.imageset/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/software-mansion-labs/typegpu-confetti/HEAD/apps/test-app/ios/ConfettiTest/Images.xcassets/SplashScreenLegacy.imageset/image.png -------------------------------------------------------------------------------- /apps/test-app/ios/ConfettiTest/Images.xcassets/SplashScreenLegacy.imageset/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/software-mansion-labs/typegpu-confetti/HEAD/apps/test-app/ios/ConfettiTest/Images.xcassets/SplashScreenLegacy.imageset/image@2x.png -------------------------------------------------------------------------------- /apps/test-app/ios/ConfettiTest/Images.xcassets/SplashScreenLegacy.imageset/image@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/software-mansion-labs/typegpu-confetti/HEAD/apps/test-app/ios/ConfettiTest/Images.xcassets/SplashScreenLegacy.imageset/image@3x.png -------------------------------------------------------------------------------- /apps/test-app/ios/ConfettiTest/ConfettiTest.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /apps/test-app/ios/ConfettiTest/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/software-mansion-labs/typegpu-confetti/HEAD/apps/test-app/ios/ConfettiTest/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png -------------------------------------------------------------------------------- /apps/test-app/android/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | #EFEFF9 3 | #EFEFF9 4 | #023c69 5 | #EFEFF9 6 | -------------------------------------------------------------------------------- /apps/test-web-app/vite.config.ts: -------------------------------------------------------------------------------- 1 | import react from '@vitejs/plugin-react'; 2 | import typegpuPlugin from 'unplugin-typegpu/vite'; 3 | import { defineConfig } from 'vite'; 4 | 5 | // https://vite.dev/config/ 6 | export default defineConfig({ 7 | plugins: [react(), typegpuPlugin({})], 8 | }); 9 | -------------------------------------------------------------------------------- /apps/test-app/android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Confetti Test 3 | contain 4 | false 5 | -------------------------------------------------------------------------------- /packages/typegpu-confetti/tsdown.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsdown'; 2 | import typegpu from 'unplugin-typegpu/rolldown'; 3 | 4 | export default defineConfig({ 5 | entry: ['src/index.ts', 'src/react/index.ts', 'src/react-native/index.ts'], 6 | format: ['esm', 'cjs'], 7 | plugins: [typegpu({})], 8 | }); 9 | -------------------------------------------------------------------------------- /apps/test-web-app/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | import './index.css'; 4 | import App from './App.tsx'; 5 | 6 | createRoot(document.getElementById('root') as HTMLDivElement).render( 7 | 8 | 9 | , 10 | ); 11 | -------------------------------------------------------------------------------- /apps/test-app/android/app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /apps/test-app/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /apps/test-app/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /apps/test-app/ios/ConfettiTest.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /apps/test-app/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /apps/test-app/index.ts: -------------------------------------------------------------------------------- 1 | import { registerRootComponent } from 'expo'; 2 | 3 | import App from './App'; 4 | 5 | // registerRootComponent calls AppRegistry.registerComponent('main', () => App); 6 | // It also ensures that whether you load the app in Expo Go or in a native build, 7 | // the environment is set up appropriately 8 | registerRootComponent(App); 9 | -------------------------------------------------------------------------------- /apps/test-app/ios/ConfettiTest/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "filename": "App-Icon-1024x1024@1x.png", 5 | "idiom": "universal", 6 | "platform": "ios", 7 | "size": "1024x1024" 8 | } 9 | ], 10 | "info": { 11 | "version": 1, 12 | "author": "expo" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /apps/test-web-app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Confetti Test 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /apps/test-web-app/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typegpu-confetti-monorepo", 3 | "private": true, 4 | "license": "MIT", 5 | "packageManager": "pnpm@10.15.0+sha512.486ebc259d3e999a4e8691ce03b5cac4a71cbeca39372a9b762cb500cfdf0873e2cb16abe3d951b1ee2cf012503f027b98b6584e4df22524e0c7450d9ec7aa7b", 6 | "scripts": { 7 | "fix": "biome check --write" 8 | }, 9 | "devDependencies": { 10 | "@biomejs/biome": "^2.3.2" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/typegpu-confetti/src/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | // slots 3 | canvasAspectRatio, 4 | deltaTime, 5 | // function shells 6 | type GravityFn, 7 | gravity, 8 | gravityFn, 9 | type InitParticleFn, 10 | initParticle, 11 | initParticleFn, 12 | maxDurationTime, 13 | maxParticleAmount, 14 | particles, 15 | time, 16 | } from './schemas'; 17 | 18 | export type { ConfettiPropTypes, ConfettiRef } from './types'; 19 | -------------------------------------------------------------------------------- /apps/test-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "expo/tsconfig.base", 3 | "compilerOptions": { 4 | "lib": ["ESNext", "DOM"], 5 | "target": "ESNext", 6 | "module": "ESNext", 7 | "strict": true, 8 | "allowImportingTsExtensions": true, 9 | "allowJs": true, 10 | "esModuleInterop": true, 11 | "moduleResolution": "bundler", 12 | "skipLibCheck": true, 13 | "types": ["@webgpu/types"] 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /apps/test-app/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /packages/typegpu-confetti/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "esModuleInterop": true, 6 | "jsx": "react-native", 7 | "lib": ["DOM", "ESNext"], 8 | "moduleResolution": "node", 9 | "noEmit": true, 10 | "resolveJsonModule": true, 11 | "skipLibCheck": true, 12 | "target": "ESNext", 13 | "noUncheckedIndexedAccess": false 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /apps/test-app/ios/ConfettiTest/Supporting/Expo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | EXUpdatesCheckOnLaunch 6 | ALWAYS 7 | EXUpdatesEnabled 8 | 9 | EXUpdatesLaunchWaitMs 10 | 0 11 | 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | .pnp 4 | .pnp.js 5 | 6 | # testing 7 | coverage 8 | 9 | # development 10 | .devcontainer 11 | .vscode 12 | 13 | # production 14 | dist 15 | build 16 | 17 | # dotenv environment variables file 18 | .env 19 | .env.local 20 | .env.development.local 21 | .env.test.local 22 | .env.production.local 23 | 24 | # logs 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | 29 | # misc 30 | .DS_Store 31 | .idea 32 | -------------------------------------------------------------------------------- /apps/test-app/ios/.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | project.xcworkspace 24 | .xcode.env.local 25 | 26 | # Bundle artifacts 27 | *.jsbundle 28 | 29 | # CocoaPods 30 | /Pods/ 31 | -------------------------------------------------------------------------------- /apps/test-app/ios/ConfettiTest/Images.xcassets/SplashScreenBackground.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors": [ 3 | { 4 | "color": { 5 | "components": { 6 | "alpha": "1.000", 7 | "blue": "0.976470588235294", 8 | "green": "0.937254901960784", 9 | "red": "0.937254901960784" 10 | }, 11 | "color-space": "srgb" 12 | }, 13 | "idiom": "universal" 14 | } 15 | ], 16 | "info": { 17 | "version": 1, 18 | "author": "expo" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /apps/test-app/ios/ConfettiTest/Images.xcassets/SplashScreenLegacy.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "idiom": "universal", 5 | "filename": "image.png", 6 | "scale": "1x" 7 | }, 8 | { 9 | "idiom": "universal", 10 | "filename": "image@2x.png", 11 | "scale": "2x" 12 | }, 13 | { 14 | "idiom": "universal", 15 | "filename": "image@3x.png", 16 | "scale": "3x" 17 | } 18 | ], 19 | "info": { 20 | "version": 1, 21 | "author": "expo" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /apps/test-app/ios/.xcode.env: -------------------------------------------------------------------------------- 1 | # This `.xcode.env` file is versioned and is used to source the environment 2 | # used when running script phases inside Xcode. 3 | # To customize your local environment, you can create an `.xcode.env.local` 4 | # file that is not versioned. 5 | 6 | # NODE_BINARY variable contains the PATH to the node executable. 7 | # 8 | # Customize the NODE_BINARY variable here. 9 | # For example, to use nvm with brew, add the following line 10 | # . "$(brew --prefix nvm)/nvm.sh" --no-use 11 | export NODE_BINARY=$(command -v node) 12 | -------------------------------------------------------------------------------- /apps/test-app/.gitignore: -------------------------------------------------------------------------------- 1 | # Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files 2 | 3 | # dependencies 4 | node_modules/ 5 | 6 | # Expo 7 | .expo/ 8 | dist/ 9 | web-build/ 10 | expo-env.d.ts 11 | 12 | # Native 13 | *.orig.* 14 | *.jks 15 | *.p8 16 | *.p12 17 | *.key 18 | *.mobileprovision 19 | 20 | # Metro 21 | .metro-health-check* 22 | 23 | # debug 24 | npm-debug.* 25 | yarn-debug.* 26 | yarn-error.* 27 | 28 | # macOS 29 | .DS_Store 30 | *.pem 31 | 32 | # local env files 33 | .env*.local 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | -------------------------------------------------------------------------------- /.zed/settings.json: -------------------------------------------------------------------------------- 1 | // Folder-specific settings 2 | // 3 | // For a full list of overridable settings, and general information on folder-specific settings, 4 | // see the documentation: https://zed.dev/docs/configuring-zed#settings-files 5 | { 6 | "languages": { 7 | "JavaScript": { 8 | "formatter": { 9 | "language_server": { 10 | "name": "biome" 11 | } 12 | } 13 | }, 14 | "TypeScript": { 15 | "formatter": { 16 | "language_server": { 17 | "name": "biome" 18 | } 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/typegpu-confetti/src/types.ts: -------------------------------------------------------------------------------- 1 | import type { GravityFn, InitParticleFn } from './schemas'; 2 | 3 | export type ConfettiPropTypes = { 4 | colorPalette?: [number, number, number, number][]; 5 | size?: number; 6 | maxDurationTime?: number | null; 7 | 8 | initParticleAmount?: number; 9 | maxParticleAmount?: number; 10 | 11 | gravity?: GravityFn; 12 | initParticle?: InitParticleFn; 13 | }; 14 | 15 | export type ConfettiRef = { 16 | pause: () => void; 17 | resume: () => void; 18 | restart: () => void; 19 | addParticles: (amount: number) => void; 20 | }; 21 | -------------------------------------------------------------------------------- /apps/test-app/android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # react-native-reanimated 11 | -keep class com.swmansion.reanimated.** { *; } 12 | -keep class com.facebook.react.turbomodule.** { *; } 13 | 14 | # Add any project specific keep options here: 15 | -------------------------------------------------------------------------------- /apps/test-app/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | -------------------------------------------------------------------------------- /apps/test-web-app/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 4 | "target": "ES2022", 5 | "lib": ["ES2023"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "isolatedModules": true, 13 | "moduleDetection": "force", 14 | "noEmit": true, 15 | 16 | /* Linting */ 17 | "strict": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "noFallthroughCasesInSwitch": true, 21 | "noUncheckedSideEffectImports": true 22 | }, 23 | "include": ["vite.config.ts"] 24 | } 25 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "${configDir}/dist", 4 | "baseUrl": "${configDir}", 5 | "declaration": true, 6 | "declarationMap": true, 7 | "lib": ["ESNext", "DOM"], 8 | "target": "ESNext", 9 | "module": "ESNext", 10 | "strict": true, 11 | "allowJs": true, 12 | "esModuleInterop": true, 13 | "moduleResolution": "bundler", 14 | "noUncheckedIndexedAccess": true, 15 | "exactOptionalPropertyTypes": true, 16 | "skipLibCheck": true, 17 | "typeRoots": [ 18 | "${configDir}/node_modules/@types", 19 | "${configDir}/node_modules/@webgpu/types" 20 | ] 21 | }, 22 | "exclude": ["${configDir}/dist", "${configDir}/node_modules"] 23 | } 24 | -------------------------------------------------------------------------------- /apps/test-app/metro.config.js: -------------------------------------------------------------------------------- 1 | const { getDefaultConfig } = require('expo/metro-config'); 2 | const path = require('node:path'); 3 | 4 | // Find the project and workspace directories 5 | const projectRoot = __dirname; 6 | // This can be replaced with `find-yarn-workspace-root` 7 | const monorepoRoot = path.resolve(projectRoot, '../..'); 8 | 9 | const config = getDefaultConfig(projectRoot); 10 | 11 | // 1. Watch all files within the monorepo 12 | config.watchFolders = [monorepoRoot]; 13 | // 2. Let Metro know where to resolve packages and in what order 14 | config.resolver.nodeModulesPaths = [ 15 | path.resolve(projectRoot, 'node_modules'), 16 | path.resolve(monorepoRoot, 'node_modules'), 17 | ]; 18 | 19 | config.resolver.unstable_enablePackageExports = true; 20 | 21 | module.exports = config; 22 | -------------------------------------------------------------------------------- /apps/test-web-app/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 4 | "target": "ES2020", 5 | "useDefineForClassFields": true, 6 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 | "module": "ESNext", 8 | "skipLibCheck": true, 9 | 10 | /* Bundler mode */ 11 | "moduleResolution": "bundler", 12 | "allowImportingTsExtensions": true, 13 | "isolatedModules": true, 14 | "moduleDetection": "force", 15 | "noEmit": true, 16 | "jsx": "react-jsx", 17 | 18 | /* Linting */ 19 | "strict": true, 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | "noFallthroughCasesInSwitch": true, 23 | "noUncheckedSideEffectImports": true 24 | }, 25 | "include": ["src"] 26 | } 27 | -------------------------------------------------------------------------------- /apps/test-web-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test-web-app", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc -b && vite build", 9 | "lint": "eslint .", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@typegpu/noise": "^0.8.0", 14 | "react": "19.2.0", 15 | "react-dom": "19.2.0", 16 | "typegpu": "^0.8.0", 17 | "typegpu-confetti": "workspace:*" 18 | }, 19 | "devDependencies": { 20 | "@types/react": "19.2.2", 21 | "@types/react-dom": "19.2.2", 22 | "@vitejs/plugin-react": "^5.1.0", 23 | "@webgpu/types": "^0.1.66", 24 | "globals": "^16.4.0", 25 | "typescript": "~5.9.3", 26 | "unplugin-typegpu": "^0.8.0", 27 | "vite": "^7.1.12" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /apps/test-app/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "Confetti Test", 4 | "slug": "test-app", 5 | "version": "1.0.0", 6 | "orientation": "portrait", 7 | "icon": "./assets/icon.png", 8 | "userInterfaceStyle": "light", 9 | "newArchEnabled": true, 10 | "splash": { 11 | "image": "./assets/splash-icon.png", 12 | "resizeMode": "contain", 13 | "backgroundColor": "#EFEFF9" 14 | }, 15 | "ios": { 16 | "supportsTablet": true, 17 | "bundleIdentifier": "com.anonymous.testapp" 18 | }, 19 | "android": { 20 | "adaptiveIcon": { 21 | "foregroundImage": "./assets/adaptive-icon.png", 22 | "backgroundColor": "#EFEFF9" 23 | }, 24 | "package": "com.anonymous.testapp" 25 | }, 26 | "web": { 27 | "favicon": "./assets/favicon.png" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/2.3.2/schema.json", 3 | "assist": { "actions": { "source": { "organizeImports": "on" } } }, 4 | "files": { 5 | "includes": [ 6 | "packages/**/*", 7 | "apps/**/*", 8 | "!**/dist/*", 9 | "!**/coverage/*", 10 | "!**/*.d.ts", 11 | "!**/*.astro" 12 | ] 13 | }, 14 | "linter": { 15 | "enabled": true, 16 | "rules": { 17 | "recommended": true, 18 | "correctness": { "noUnusedImports": "error" } 19 | } 20 | }, 21 | "json": { 22 | "formatter": { 23 | "indentStyle": "space" 24 | } 25 | }, 26 | "javascript": { 27 | "formatter": { 28 | "indentStyle": "space", 29 | "quoteStyle": "single" 30 | } 31 | }, 32 | "css": { 33 | "formatter": { 34 | "indentStyle": "space", 35 | "quoteStyle": "single" 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /apps/test-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test-app", 3 | "version": "1.0.0", 4 | "main": "index.ts", 5 | "scripts": { 6 | "start": "expo start", 7 | "android": "expo run:android", 8 | "ios": "expo run:ios", 9 | "web": "expo start --web" 10 | }, 11 | "dependencies": { 12 | "@babel/core": "^7.28.5", 13 | "@typegpu/noise": "^0.8.0", 14 | "expo": "~54.0.21", 15 | "react": "19.2.0", 16 | "react-native": "^0.81.5", 17 | "react-native-safe-area-context": "^5.6.2", 18 | "react-native-wgpu": "^0.4.1", 19 | "typegpu": "^0.8.0", 20 | "typegpu-confetti": "workspace:*" 21 | }, 22 | "devDependencies": { 23 | "@biomejs/biome": "^2.3.2", 24 | "@types/react": "~19.2.2", 25 | "@webgpu/types": "^0.1.66", 26 | "typescript": "^5.9.3", 27 | "unplugin-typegpu": "^0.8.0" 28 | }, 29 | "private": true, 30 | "packageManager": "pnpm@10.15.0+sha512.486ebc259d3e999a4e8691ce03b5cac4a71cbeca39372a9b762cb500cfdf0873e2cb16abe3d951b1ee2cf012503f027b98b6584e4df22524e0c7450d9ec7aa7b" 31 | } 32 | -------------------------------------------------------------------------------- /apps/test-app/android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | google() 6 | mavenCentral() 7 | } 8 | dependencies { 9 | classpath('com.android.tools.build:gradle') 10 | classpath('com.facebook.react:react-native-gradle-plugin') 11 | classpath('org.jetbrains.kotlin:kotlin-gradle-plugin') 12 | } 13 | } 14 | 15 | def reactNativeAndroidDir = new File( 16 | providers.exec { 17 | workingDir(rootDir) 18 | commandLine("node", "--print", "require.resolve('react-native/package.json')") 19 | }.standardOutput.asText.get().trim(), 20 | "../android" 21 | ) 22 | 23 | allprojects { 24 | repositories { 25 | maven { 26 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 27 | url(reactNativeAndroidDir) 28 | } 29 | 30 | google() 31 | mavenCentral() 32 | maven { url 'https://www.jitpack.io' } 33 | } 34 | } 35 | 36 | apply plugin: "expo-root-project" 37 | apply plugin: "com.facebook.react.rootproject" 38 | -------------------------------------------------------------------------------- /packages/typegpu-confetti/src/defaults.ts: -------------------------------------------------------------------------------- 1 | import { randf } from '@typegpu/noise'; 2 | import * as d from 'typegpu/data'; 3 | import { particles } from './schemas'; 4 | import type { ConfettiPropTypes } from './types'; 5 | 6 | export const defaults: { 7 | [K in keyof ConfettiPropTypes]-?: NonNullable; 8 | } = { 9 | maxDurationTime: 2, 10 | initParticleAmount: 200, 11 | maxParticleAmount: 1000, 12 | 13 | size: 1, 14 | colorPalette: [ 15 | [154, 177, 155, 1], 16 | [67, 129, 193, 1], 17 | [99, 71, 77, 1], 18 | [239, 121, 138, 1], 19 | [255, 166, 48, 1], 20 | ], 21 | 22 | gravity: () => { 23 | 'use gpu'; 24 | return d.vec2f(0, -0.3); 25 | }, 26 | 27 | initParticle: (i) => { 28 | 'use gpu'; 29 | const particle = particles.value[i]; 30 | 31 | particle.position = d.vec2f( 32 | randf.sample() * 2 - 1, 33 | randf.sample() / 1.5 + 1, 34 | ); 35 | particle.velocity = d.vec2f( 36 | randf.sample() * 2 - 1, 37 | -(randf.sample() / 25 + 0.01) * 50, 38 | ); 39 | 40 | particles.value[i] = particle; 41 | }, 42 | }; 43 | -------------------------------------------------------------------------------- /packages/typegpu-confetti/src/react-native/utils.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import { PixelRatio } from 'react-native'; 3 | import { type RNCanvasContext, useCanvasRef } from 'react-native-wgpu'; 4 | import { useRoot } from '../utils'; 5 | 6 | export function useGPUSetup( 7 | presentationFormat: GPUTextureFormat = navigator.gpu.getPreferredCanvasFormat(), 8 | ) { 9 | const root = useRoot(); 10 | const [context, setContext] = useState(null); 11 | const canvasRef = useCanvasRef(); 12 | 13 | useEffect(() => { 14 | const ctx = canvasRef.current.getContext('webgpu'); 15 | 16 | if (!ctx) { 17 | setContext(null); 18 | return; 19 | } 20 | 21 | const canvas = ctx.canvas as HTMLCanvasElement; 22 | canvas.width = canvas.clientWidth * PixelRatio.get(); 23 | canvas.height = canvas.clientHeight * PixelRatio.get(); 24 | 25 | ctx.configure({ 26 | device: root.device, 27 | format: presentationFormat, 28 | alphaMode: 'premultiplied', 29 | }); 30 | 31 | setContext(ctx); 32 | }, [presentationFormat, root, canvasRef]); 33 | 34 | return { canvasRef, context }; 35 | } 36 | -------------------------------------------------------------------------------- /apps/test-web-app/src/index.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | :root { 6 | font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; 7 | } 8 | 9 | body { 10 | margin: 0; 11 | display: grid; 12 | place-items: center; 13 | min-height: 100vh; 14 | background-color: rgb(239 239 249); 15 | } 16 | 17 | h1 { 18 | font-weight: 900; 19 | font-size: 25; 20 | text-align: left; 21 | padding-left: 10%; 22 | padding-bottom: 40; 23 | font-style: italic; 24 | width: 100%; 25 | color: rgb(82 89 238); 26 | } 27 | 28 | .container { 29 | display: flex; 30 | flex: 1; 31 | flex-direction: column; 32 | justify-content: center; 33 | align-items: center; 34 | padding: 20px; 35 | } 36 | 37 | button { 38 | border: none; 39 | font-family: inherit; 40 | cursor: pointer; 41 | border-radius: 20px; 42 | background-color: rgb(82 89 238); 43 | padding: 15px; 44 | } 45 | 46 | .row { 47 | display: flex; 48 | flex-direction: row; 49 | justify-content: center; 50 | align-items: center; 51 | gap: 8px; 52 | flex-wrap: wrap; 53 | } 54 | 55 | .label { 56 | font-weight: 600; 57 | color: white; 58 | } 59 | 60 | .icon { 61 | font-size: 1.5rem; 62 | } 63 | -------------------------------------------------------------------------------- /packages/typegpu-confetti/src/react/ConfettiProvider.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | type CSSProperties, 3 | createContext, 4 | type ReactNode, 5 | type RefObject, 6 | useContext, 7 | useRef, 8 | } from 'react'; 9 | import type { ConfettiPropTypes, ConfettiRef } from '../types'; 10 | import Confetti from './Confetti'; 11 | 12 | const ConfettiContext = createContext | null>( 13 | null, 14 | ); 15 | 16 | export function ConfettiProvider( 17 | props: { children: ReactNode; style?: CSSProperties } & ConfettiPropTypes, 18 | ) { 19 | const { children, ...confettiProps } = props; 20 | const ref = useRef(null); 21 | 22 | return ( 23 | 24 |
25 | {children} 26 | 27 | 33 |
34 |
35 | ); 36 | } 37 | 38 | export function useConfetti() { 39 | return useContext(ConfettiContext); 40 | } 41 | -------------------------------------------------------------------------------- /packages/typegpu-confetti/src/react-native/ConfettiProvider.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | createContext, 3 | type ReactNode, 4 | type RefObject, 5 | useContext, 6 | useRef, 7 | } from 'react'; 8 | import { type StyleProp, View, type ViewStyle } from 'react-native'; 9 | import type { ConfettiPropTypes, ConfettiRef } from '../types'; 10 | import Confetti from './Confetti'; 11 | 12 | const ConfettiContext = createContext | null>( 13 | null, 14 | ); 15 | 16 | export function ConfettiProvider( 17 | props: { children: ReactNode } & ConfettiPropTypes & { 18 | style?: StyleProp; 19 | }, 20 | ) { 21 | const { children, ...confettiProps } = props; 22 | const ref = useRef(null); 23 | 24 | return ( 25 | 26 | 27 | {children} 28 | 29 | 35 | 36 | 37 | ); 38 | } 39 | 40 | export function useConfetti() { 41 | return useContext(ConfettiContext); 42 | } 43 | -------------------------------------------------------------------------------- /apps/test-app/android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def reactNativeGradlePlugin = new File( 3 | providers.exec { 4 | workingDir(rootDir) 5 | commandLine("node", "--print", "require.resolve('@react-native/gradle-plugin/package.json', { paths: [require.resolve('react-native/package.json')] })") 6 | }.standardOutput.asText.get().trim() 7 | ).getParentFile().absolutePath 8 | includeBuild(reactNativeGradlePlugin) 9 | 10 | def expoPluginsPath = new File( 11 | providers.exec { 12 | workingDir(rootDir) 13 | commandLine("node", "--print", "require.resolve('expo-modules-autolinking/package.json', { paths: [require.resolve('expo/package.json')] })") 14 | }.standardOutput.asText.get().trim(), 15 | "../android/expo-gradle-plugin" 16 | ).absolutePath 17 | includeBuild(expoPluginsPath) 18 | } 19 | 20 | plugins { 21 | id("com.facebook.react.settings") 22 | id("expo-autolinking-settings") 23 | } 24 | 25 | extensions.configure(com.facebook.react.ReactSettingsExtension) { ex -> 26 | if (System.getenv('EXPO_USE_COMMUNITY_AUTOLINKING') == '1') { 27 | ex.autolinkLibrariesFromCommand() 28 | } else { 29 | ex.autolinkLibrariesFromCommand(expoAutolinking.rnConfigCommand) 30 | } 31 | } 32 | expoAutolinking.useExpoModules() 33 | 34 | rootProject.name = 'Confetti Test' 35 | 36 | expoAutolinking.useExpoVersionCatalog() 37 | 38 | include ':app' 39 | includeBuild(expoAutolinking.reactNativeGradlePlugin) 40 | -------------------------------------------------------------------------------- /apps/test-app/ios/ConfettiTest/PrivacyInfo.xcprivacy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrivacyAccessedAPITypes 6 | 7 | 8 | NSPrivacyAccessedAPIType 9 | NSPrivacyAccessedAPICategoryUserDefaults 10 | NSPrivacyAccessedAPITypeReasons 11 | 12 | CA92.1 13 | 14 | 15 | 16 | NSPrivacyAccessedAPIType 17 | NSPrivacyAccessedAPICategoryFileTimestamp 18 | NSPrivacyAccessedAPITypeReasons 19 | 20 | 0A2A.1 21 | 3B52.1 22 | C617.1 23 | 24 | 25 | 26 | NSPrivacyAccessedAPIType 27 | NSPrivacyAccessedAPICategoryDiskSpace 28 | NSPrivacyAccessedAPITypeReasons 29 | 30 | E174.1 31 | 85F4.1 32 | 33 | 34 | 35 | NSPrivacyAccessedAPIType 36 | NSPrivacyAccessedAPICategorySystemBootTime 37 | NSPrivacyAccessedAPITypeReasons 38 | 39 | 35F9.1 40 | 41 | 42 | 43 | NSPrivacyCollectedDataTypes 44 | 45 | NSPrivacyTracking 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /apps/test-app/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /apps/test-app/android/app/src/main/res/drawable/rn_edit_text_material.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 22 | 23 | 24 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /apps/test-app/android/app/src/main/java/com/anonymous/testapp/MainApplication.kt: -------------------------------------------------------------------------------- 1 | package com.anonymous.testapp 2 | 3 | import android.app.Application 4 | import android.content.res.Configuration 5 | 6 | import com.facebook.react.PackageList 7 | import com.facebook.react.ReactApplication 8 | import com.facebook.react.ReactNativeHost 9 | import com.facebook.react.ReactPackage 10 | import com.facebook.react.ReactHost 11 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load 12 | import com.facebook.react.defaults.DefaultReactNativeHost 13 | import com.facebook.react.soloader.OpenSourceMergedSoMapping 14 | import com.facebook.soloader.SoLoader 15 | 16 | import expo.modules.ApplicationLifecycleDispatcher 17 | import expo.modules.ReactNativeHostWrapper 18 | 19 | class MainApplication : Application(), ReactApplication { 20 | 21 | override val reactNativeHost: ReactNativeHost = ReactNativeHostWrapper( 22 | this, 23 | object : DefaultReactNativeHost(this) { 24 | override fun getPackages(): List { 25 | val packages = PackageList(this).packages 26 | // Packages that cannot be autolinked yet can be added manually here, for example: 27 | // packages.add(MyReactNativePackage()) 28 | return packages 29 | } 30 | 31 | override fun getJSMainModuleName(): String = ".expo/.virtual-metro-entry" 32 | 33 | override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG 34 | 35 | override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED 36 | override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED 37 | } 38 | ) 39 | 40 | override val reactHost: ReactHost 41 | get() = ReactNativeHostWrapper.createReactHost(applicationContext, reactNativeHost) 42 | 43 | override fun onCreate() { 44 | super.onCreate() 45 | SoLoader.init(this, OpenSourceMergedSoMapping) 46 | if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { 47 | // If you opted-in for the New Architecture, we load the native entry point for this app. 48 | load() 49 | } 50 | ApplicationLifecycleDispatcher.onApplicationCreate(this) 51 | } 52 | 53 | override fun onConfigurationChanged(newConfig: Configuration) { 54 | super.onConfigurationChanged(newConfig) 55 | ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /apps/test-app/android/app/src/main/java/com/anonymous/testapp/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.anonymous.testapp 2 | 3 | import android.os.Build 4 | import android.os.Bundle 5 | 6 | import com.facebook.react.ReactActivity 7 | import com.facebook.react.ReactActivityDelegate 8 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled 9 | import com.facebook.react.defaults.DefaultReactActivityDelegate 10 | 11 | import expo.modules.ReactActivityDelegateWrapper 12 | 13 | class MainActivity : ReactActivity() { 14 | override fun onCreate(savedInstanceState: Bundle?) { 15 | // Set the theme to AppTheme BEFORE onCreate to support 16 | // coloring the background, status bar, and navigation bar. 17 | // This is required for expo-splash-screen. 18 | setTheme(R.style.AppTheme); 19 | super.onCreate(null) 20 | } 21 | 22 | /** 23 | * Returns the name of the main component registered from JavaScript. This is used to schedule 24 | * rendering of the component. 25 | */ 26 | override fun getMainComponentName(): String = "main" 27 | 28 | /** 29 | * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate] 30 | * which allows you to enable New Architecture with a single boolean flags [fabricEnabled] 31 | */ 32 | override fun createReactActivityDelegate(): ReactActivityDelegate { 33 | return ReactActivityDelegateWrapper( 34 | this, 35 | BuildConfig.IS_NEW_ARCHITECTURE_ENABLED, 36 | object : DefaultReactActivityDelegate( 37 | this, 38 | mainComponentName, 39 | fabricEnabled 40 | ){}) 41 | } 42 | 43 | /** 44 | * Align the back button behavior with Android S 45 | * where moving root activities to background instead of finishing activities. 46 | * @see onBackPressed 47 | */ 48 | override fun invokeDefaultOnBackPressed() { 49 | if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) { 50 | if (!moveTaskToBack(false)) { 51 | // For non-root activities, use the default implementation to finish them. 52 | super.invokeDefaultOnBackPressed() 53 | } 54 | return 55 | } 56 | 57 | // Use the default back button implementation on Android S 58 | // because it's doing more than [Activity.moveTaskToBack] in fact. 59 | super.invokeDefaultOnBackPressed() 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /apps/test-app/ios/ConfettiTest/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Expo 2 | import React 3 | import ReactAppDependencyProvider 4 | 5 | @UIApplicationMain 6 | public class AppDelegate: ExpoAppDelegate { 7 | var window: UIWindow? 8 | 9 | var reactNativeDelegate: ExpoReactNativeFactoryDelegate? 10 | var reactNativeFactory: RCTReactNativeFactory? 11 | 12 | public override func application( 13 | _ application: UIApplication, 14 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil 15 | ) -> Bool { 16 | let delegate = ReactNativeDelegate() 17 | let factory = ExpoReactNativeFactory(delegate: delegate) 18 | delegate.dependencyProvider = RCTAppDependencyProvider() 19 | 20 | reactNativeDelegate = delegate 21 | reactNativeFactory = factory 22 | bindReactNativeFactory(factory) 23 | 24 | #if os(iOS) || os(tvOS) 25 | window = UIWindow(frame: UIScreen.main.bounds) 26 | factory.startReactNative( 27 | withModuleName: "main", 28 | in: window, 29 | launchOptions: launchOptions) 30 | #endif 31 | 32 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 33 | } 34 | 35 | // Linking API 36 | public override func application( 37 | _ app: UIApplication, 38 | open url: URL, 39 | options: [UIApplication.OpenURLOptionsKey: Any] = [:] 40 | ) -> Bool { 41 | return super.application(app, open: url, options: options) || RCTLinkingManager.application(app, open: url, options: options) 42 | } 43 | 44 | // Universal Links 45 | public override func application( 46 | _ application: UIApplication, 47 | continue userActivity: NSUserActivity, 48 | restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void 49 | ) -> Bool { 50 | let result = RCTLinkingManager.application(application, continue: userActivity, restorationHandler: restorationHandler) 51 | return super.application(application, continue: userActivity, restorationHandler: restorationHandler) || result 52 | } 53 | } 54 | 55 | class ReactNativeDelegate: ExpoReactNativeFactoryDelegate { 56 | // Extension point for config-plugins 57 | 58 | override func sourceURL(for bridge: RCTBridge) -> URL? { 59 | // needed to return the correct URL for expo-dev-client. 60 | bridge.bundleURL ?? bundleURL() 61 | } 62 | 63 | override func bundleURL() -> URL? { 64 | #if DEBUG 65 | return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: ".expo/.virtual-metro-entry") 66 | #else 67 | return Bundle.main.url(forResource: "main", withExtension: "jsbundle") 68 | #endif 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /apps/test-app/ios/Podfile: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(`node --print "require.resolve('expo/package.json')"`), "scripts/autolinking") 2 | require File.join(File.dirname(`node --print "require.resolve('react-native/package.json')"`), "scripts/react_native_pods") 3 | 4 | require 'json' 5 | podfile_properties = JSON.parse(File.read(File.join(__dir__, 'Podfile.properties.json'))) rescue {} 6 | 7 | def ccache_enabled?(podfile_properties) 8 | # Environment variable takes precedence 9 | return ENV['USE_CCACHE'] == '1' if ENV['USE_CCACHE'] 10 | 11 | # Fall back to Podfile properties 12 | podfile_properties['apple.ccacheEnabled'] == 'true' 13 | end 14 | 15 | ENV['RCT_NEW_ARCH_ENABLED'] ||= '0' if podfile_properties['newArchEnabled'] == 'false' 16 | ENV['EX_DEV_CLIENT_NETWORK_INSPECTOR'] ||= podfile_properties['EX_DEV_CLIENT_NETWORK_INSPECTOR'] 17 | ENV['RCT_USE_RN_DEP'] ||= '1' if podfile_properties['ios.buildReactNativeFromSource'] != 'true' && podfile_properties['newArchEnabled'] != 'false' 18 | ENV['RCT_USE_PREBUILT_RNCORE'] ||= '1' if podfile_properties['ios.buildReactNativeFromSource'] != 'true' && podfile_properties['newArchEnabled'] != 'false' 19 | platform :ios, podfile_properties['ios.deploymentTarget'] || '15.1' 20 | 21 | prepare_react_native_project! 22 | 23 | target 'ConfettiTest' do 24 | use_expo_modules! 25 | 26 | if ENV['EXPO_USE_COMMUNITY_AUTOLINKING'] == '1' 27 | config_command = ['node', '-e', "process.argv=['', '', 'config'];require('@react-native-community/cli').run()"]; 28 | else 29 | config_command = [ 30 | 'npx', 31 | 'expo-modules-autolinking', 32 | 'react-native-config', 33 | '--json', 34 | '--platform', 35 | 'ios' 36 | ] 37 | end 38 | 39 | config = use_native_modules!(config_command) 40 | 41 | use_frameworks! :linkage => podfile_properties['ios.useFrameworks'].to_sym if podfile_properties['ios.useFrameworks'] 42 | use_frameworks! :linkage => ENV['USE_FRAMEWORKS'].to_sym if ENV['USE_FRAMEWORKS'] 43 | 44 | use_react_native!( 45 | :path => config[:reactNativePath], 46 | :hermes_enabled => podfile_properties['expo.jsEngine'] == nil || podfile_properties['expo.jsEngine'] == 'hermes', 47 | # An absolute path to your application root. 48 | :app_path => "#{Pod::Config.instance.installation_root}/..", 49 | :privacy_file_aggregation_enabled => podfile_properties['apple.privacyManifestAggregationEnabled'] != 'false', 50 | ) 51 | 52 | post_install do |installer| 53 | react_native_post_install( 54 | installer, 55 | config[:reactNativePath], 56 | :mac_catalyst_enabled => false, 57 | :ccache_enabled => ccache_enabled?(podfile_properties), 58 | ) 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /packages/typegpu-confetti/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typegpu-confetti", 3 | "version": "0.2.0", 4 | "description": "Customizable confetti animation component for React and React Native, running on the GPU", 5 | "license": "MIT", 6 | "type": "module", 7 | "exports": { 8 | ".": "./src/index.ts", 9 | "./react-native": "./src/react-native/index.ts", 10 | "./react": "./src/react/index.ts", 11 | "./package.json": "./package.json" 12 | }, 13 | "main": "./src/index.ts", 14 | "types": "./src/index.ts", 15 | "publishConfig": { 16 | "directory": "dist", 17 | "linkDirectory": false, 18 | "main": "./dist/index.js", 19 | "types": "./dist/index.d.ts", 20 | "exports": { 21 | "./package.json": "./package.json", 22 | ".": { 23 | "types": "./dist/index.d.ts", 24 | "module": "./dist/index.js", 25 | "import": "./dist/index.js", 26 | "default": "./dist/index.cjs" 27 | }, 28 | "./react-native": { 29 | "types": "./dist/react-native/index.d.ts", 30 | "module": "./dist/react-native/index.js", 31 | "import": "./dist/react-native/index.js", 32 | "default": "./dist/react-native/index.cjs" 33 | }, 34 | "./react": { 35 | "types": "./dist/react/index.d.ts", 36 | "module": "./dist/react/index.js", 37 | "import": "./dist/react/index.js", 38 | "default": "./dist/react/index.cjs" 39 | } 40 | } 41 | }, 42 | "sideEffects": false, 43 | "scripts": { 44 | "build": "node prepack.mjs", 45 | "prepublishOnly": "pnpm run build" 46 | }, 47 | "engines": { 48 | "node": ">=22.0.0" 49 | }, 50 | "repository": { 51 | "type": "git", 52 | "url": "git+https://github.com/software-mansion-labs/typegpu-confetti.git" 53 | }, 54 | "keywords": [ 55 | "confetti", 56 | "javascript", 57 | "react-native", 58 | "webgpu", 59 | "react" 60 | ], 61 | "bugs": { 62 | "url": "https://github.com/software-mansion-labs/typegpu-confetti/issues" 63 | }, 64 | "dependencies": { 65 | "@typegpu/noise": "^0.8.0", 66 | "typegpu": "^0.8.0" 67 | }, 68 | "peerDependencies": { 69 | "react": "*", 70 | "react-native": "*", 71 | "react-native-wgpu": "*" 72 | }, 73 | "devDependencies": { 74 | "@webgpu/types": "^0.1.66", 75 | "execa": "^9.6.0", 76 | "react": "19.2.0", 77 | "react-native": "^0.81.5", 78 | "react-native-wgpu": "^0.4.1", 79 | "remeda": "^2.32.0", 80 | "tsdown": "^0.15.12", 81 | "typescript": "^5.9.3", 82 | "unplugin-typegpu": "^0.8.0" 83 | }, 84 | "packageManager": "pnpm@10.15.0+sha512.486ebc259d3e999a4e8691ce03b5cac4a71cbeca39372a9b762cb500cfdf0873e2cb16abe3d951b1ee2cf012503f027b98b6584e4df22524e0c7450d9ec7aa7b" 85 | } 86 | -------------------------------------------------------------------------------- /apps/test-app/ios/ConfettiTest/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CADisableMinimumFrameDurationOnPhone 6 | 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 9 | CFBundleDisplayName 10 | Confetti Test 11 | CFBundleExecutable 12 | $(EXECUTABLE_NAME) 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | $(PRODUCT_NAME) 19 | CFBundlePackageType 20 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 21 | CFBundleShortVersionString 22 | 1.0.0 23 | CFBundleSignature 24 | ???? 25 | CFBundleURLTypes 26 | 27 | 28 | CFBundleURLSchemes 29 | 30 | com.anonymous.testapp 31 | 32 | 33 | 34 | CFBundleVersion 35 | 1 36 | LSMinimumSystemVersion 37 | 12.0 38 | LSRequiresIPhoneOS 39 | 40 | NSAppTransportSecurity 41 | 42 | NSAllowsArbitraryLoads 43 | 44 | NSAllowsLocalNetworking 45 | 46 | 47 | RCTNewArchEnabled 48 | 49 | UILaunchStoryboardName 50 | SplashScreen 51 | UIRequiredDeviceCapabilities 52 | 53 | arm64 54 | 55 | UIRequiresFullScreen 56 | 57 | UIStatusBarStyle 58 | UIStatusBarStyleDefault 59 | UISupportedInterfaceOrientations 60 | 61 | UIInterfaceOrientationPortrait 62 | UIInterfaceOrientationPortraitUpsideDown 63 | 64 | UISupportedInterfaceOrientations~ipad 65 | 66 | UIInterfaceOrientationPortrait 67 | UIInterfaceOrientationPortraitUpsideDown 68 | UIInterfaceOrientationLandscapeLeft 69 | UIInterfaceOrientationLandscapeRight 70 | 71 | UIUserInterfaceStyle 72 | Light 73 | UIViewControllerBasedStatusBarAppearance 74 | 75 | 76 | -------------------------------------------------------------------------------- /apps/test-app/android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx512m -XX:MaxMetaspaceSize=256m 13 | org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | # AndroidX package structure to make it clearer which packages are bundled with the 21 | # Android operating system, and which are packaged with your app's APK 22 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 23 | android.useAndroidX=true 24 | 25 | # Enable AAPT2 PNG crunching 26 | android.enablePngCrunchInReleaseBuilds=true 27 | 28 | # Use this property to specify which architecture you want to build. 29 | # You can also override it from the CLI using 30 | # ./gradlew -PreactNativeArchitectures=x86_64 31 | reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 32 | 33 | # Use this property to enable support to the new architecture. 34 | # This will allow you to use TurboModules and the Fabric render in 35 | # your application. You should enable this flag either if you want 36 | # to write custom TurboModules/Fabric components OR use libraries that 37 | # are providing them. 38 | newArchEnabled=true 39 | 40 | # Use this property to enable or disable the Hermes JS engine. 41 | # If set to false, you will be using JSC instead. 42 | hermesEnabled=true 43 | 44 | # Enable GIF support in React Native images (~200 B increase) 45 | expo.gif.enabled=true 46 | # Enable webp support in React Native images (~85 KB increase) 47 | expo.webp.enabled=true 48 | # Enable animated webp support (~3.4 MB increase) 49 | # Disabled by default because iOS doesn't support animated webp 50 | expo.webp.animated=false 51 | 52 | # Enable network inspector 53 | EX_DEV_CLIENT_NETWORK_INSPECTOR=true 54 | 55 | # Use legacy packaging to compress native libraries in the resulting APK. 56 | expo.useLegacyPackaging=false 57 | 58 | expo.edgeToEdgeEnabled=true 59 | # Use this property to enable edge-to-edge display support. 60 | # This allows your app to draw behind system bars for an immersive UI. 61 | # Note: Only works with ReactActivity and should not be used with custom Activity. 62 | edgeToEdgeEnabled=true 63 | -------------------------------------------------------------------------------- /apps/test-app/android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /apps/test-app/ios/ConfettiTest.xcodeproj/xcshareddata/xcschemes/ConfettiTest.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 54 | 56 | 62 | 63 | 64 | 65 | 71 | 73 | 79 | 80 | 81 | 82 | 84 | 85 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /packages/typegpu-confetti/prepack.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | /* 3 | * Used as a pre-publishing step. 4 | */ 5 | 6 | import * as fs from 'node:fs/promises'; 7 | import process from 'node:process'; 8 | import { execa } from 'execa'; 9 | import { entries, mapValues } from 'remeda'; 10 | 11 | const cwd = new URL(`file:${process.cwd()}/`); 12 | 13 | /** 14 | * @param {*} value 15 | * @param {(path: string, value: string) => string} transform 16 | * @param {string=} path 17 | * @returns {*} 18 | */ 19 | function deepMapStrings(value, transform, path = '') { 20 | if (value === undefined || value === null) { 21 | return value; 22 | } 23 | 24 | if (Array.isArray(value)) { 25 | return value; 26 | } 27 | 28 | if (typeof value === 'object') { 29 | return Object.fromEntries( 30 | Object.entries(value).map(([key, val]) => [ 31 | key, 32 | deepMapStrings(val, transform, `${path}.${key}`), 33 | ]), 34 | ); 35 | } 36 | 37 | if (typeof value === 'string') { 38 | return transform(path, value); 39 | } 40 | 41 | return value; 42 | } 43 | 44 | async function transformPackageJSON() { 45 | const packageJsonUrl = new URL('./package.json', cwd); 46 | const distPackageJsonUrl = new URL('./dist/package.json', cwd); 47 | 48 | const packageJson = JSON.parse( 49 | await fs.readFile(packageJsonUrl.pathname, 'utf-8'), 50 | ); 51 | let distPackageJson = structuredClone(packageJson); 52 | 53 | // Replacing `exports`, `main`, and `types` with `publishConfig.*` 54 | if (distPackageJson.publishConfig?.main) { 55 | distPackageJson.main = distPackageJson.publishConfig.main; 56 | } 57 | if (distPackageJson.publishConfig?.types) { 58 | distPackageJson.types = distPackageJson.publishConfig.types; 59 | } 60 | if (distPackageJson.publishConfig?.exports) { 61 | distPackageJson.exports = distPackageJson.publishConfig.exports; 62 | } 63 | distPackageJson.publishConfig = undefined; 64 | 65 | // Altering paths in the package.json 66 | distPackageJson = deepMapStrings(distPackageJson, (_path, value) => { 67 | if (value.startsWith('./dist/')) { 68 | return value.replace(/^\.\/dist/, '.'); 69 | } 70 | if (value.startsWith('./src/')) { 71 | return value.replace(/^\.\/src/, '.'); 72 | } 73 | return value; 74 | }); 75 | 76 | // Erroring out on any wildcard dependencies 77 | for (const [moduleKey, versionSpec] of [ 78 | ...entries(distPackageJson.dependencies ?? {}), 79 | ]) { 80 | if (versionSpec === '*' || versionSpec === 'workspace:*') { 81 | throw new Error( 82 | `Cannot depend on a module with a wildcard version. (${moduleKey}: ${versionSpec})`, 83 | ); 84 | } 85 | } 86 | 87 | distPackageJson.private = false; 88 | distPackageJson.scripts = {}; 89 | // Removing dev dependencies. 90 | distPackageJson.devDependencies = undefined; 91 | // Removing workspace specifiers in dependencies. 92 | distPackageJson.dependencies = mapValues( 93 | distPackageJson.dependencies ?? {}, 94 | (/** @type {string} */ value) => value.replace(/^workspace:/, ''), 95 | ); 96 | distPackageJson.peerDependencies = mapValues( 97 | distPackageJson.peerDependencies ?? {}, 98 | (/** @type {string} */ value) => value.replace(/^workspace:/, ''), 99 | ); 100 | 101 | await fs.writeFile( 102 | distPackageJsonUrl.pathname, 103 | JSON.stringify(distPackageJson, undefined, 2), 104 | 'utf-8', 105 | ); 106 | } 107 | 108 | console.log('Preparing the package for publishing'); 109 | 110 | const $ = execa({ all: true }); 111 | await $`pnpm tsdown`; 112 | await $`cp ./README.md ./dist/README.md`; 113 | await transformPackageJSON(); 114 | 115 | console.log('Package prepared!'); 116 | -------------------------------------------------------------------------------- /apps/test-app/ios/ConfettiTest/SplashScreen.storyboard: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /packages/typegpu-confetti/src/utils.ts: -------------------------------------------------------------------------------- 1 | import { 2 | useCallback, 3 | useContext, 4 | useEffect, 5 | useLayoutEffect, 6 | useMemo, 7 | useRef, 8 | useState, 9 | } from 'react'; 10 | import type { TgpuBuffer, TgpuRoot, ValidateBufferSchema } from 'typegpu'; 11 | import type { AnyData, Infer } from 'typegpu/data'; 12 | import { RootContext } from './context'; 13 | 14 | export function useRoot(): TgpuRoot { 15 | const root = useContext(RootContext); 16 | 17 | if (root === null) { 18 | throw new Error( 19 | 'No root (tgpu.init) object passed via context to the component.', 20 | ); 21 | } 22 | return root; 23 | } 24 | 25 | export function useBuffer( 26 | schema: ValidateBufferSchema, 27 | value?: Infer | undefined, 28 | ): TgpuBuffer { 29 | const root = useRoot(); 30 | const bufferRef = useRef | null>(null); 31 | 32 | // biome-ignore lint/correctness/useExhaustiveDependencies: 33 | let buffer = useMemo(() => { 34 | if (bufferRef.current) { 35 | bufferRef.current.destroy(); 36 | } 37 | const buffer = root.createBuffer(schema, value); 38 | bufferRef.current = buffer; 39 | return buffer; 40 | }, [root, schema]); 41 | 42 | // biome-ignore lint/style/noNonNullAssertion: 43 | buffer = bufferRef.current!; 44 | 45 | useLayoutEffect(() => { 46 | if (value !== undefined && buffer && !buffer.destroyed) { 47 | buffer.write(value); 48 | } 49 | }, [value, buffer]); 50 | 51 | const cleanupRef = useRef | null>(null); 52 | 53 | useEffect(() => { 54 | if (cleanupRef.current !== null) { 55 | clearTimeout(cleanupRef.current); 56 | } 57 | 58 | return () => { 59 | cleanupRef.current = setTimeout(() => { 60 | buffer.destroy(); 61 | }, 1000); 62 | }; 63 | }, [buffer]); 64 | 65 | return buffer; 66 | } 67 | 68 | // biome-ignore lint/suspicious/noExplicitAny: it's fine 69 | function useEvent any>( 70 | handler: TFunction, 71 | ) { 72 | const handlerRef = useRef(handler); 73 | 74 | useLayoutEffect(() => { 75 | handlerRef.current = handler; 76 | }); 77 | 78 | return useCallback((...args: Parameters) => { 79 | const fn = handlerRef.current; 80 | return fn(...args); 81 | }, []) as TFunction; 82 | } 83 | 84 | export function useFrame( 85 | loop: (deltaTime: number) => unknown, 86 | isRunning = true, 87 | ) { 88 | const loopEvent = useEvent(loop); 89 | useEffect(() => { 90 | if (!isRunning) { 91 | return; 92 | } 93 | 94 | let lastTime = Date.now(); 95 | 96 | const runner = () => { 97 | const now = Date.now(); 98 | const dt = now - lastTime; 99 | lastTime = now; 100 | loopEvent(dt); 101 | frame = requestAnimationFrame(runner); 102 | }; 103 | 104 | let frame = requestAnimationFrame(runner); 105 | 106 | return () => cancelAnimationFrame(frame); 107 | }, [loopEvent, isRunning]); 108 | } 109 | 110 | interface DeviceContext { 111 | device: GPUDevice; 112 | adapter: GPUAdapter; 113 | } 114 | 115 | export const useDevice = ( 116 | adapterOptions?: GPURequestAdapterOptions, 117 | deviceDescriptor?: GPUDeviceDescriptor, 118 | ) => { 119 | const [state, setState] = useState(null); 120 | 121 | useEffect(() => { 122 | setState(null); // resetting old adapter and device 123 | 124 | let deviceContext: Promise | DeviceContext = (async () => { 125 | const adapter = await navigator.gpu.requestAdapter(adapterOptions); 126 | if (!adapter) { 127 | throw new Error('No appropriate GPUAdapter found.'); 128 | } 129 | const device = await adapter.requestDevice(deviceDescriptor); 130 | if (!device) { 131 | throw new Error('No appropriate GPUDevice found.'); 132 | } 133 | deviceContext = { adapter, device }; 134 | setState(deviceContext); 135 | return deviceContext; 136 | })(); 137 | 138 | return () => { 139 | if (deviceContext instanceof Promise) { 140 | deviceContext.then((dev) => dev.device.destroy()); 141 | } else { 142 | deviceContext.device.destroy(); 143 | } 144 | }; 145 | }, [adapterOptions, deviceDescriptor]); 146 | 147 | return { adapter: state?.adapter ?? null, device: state?.device ?? null }; 148 | }; 149 | -------------------------------------------------------------------------------- /packages/typegpu-confetti/src/schemas.ts: -------------------------------------------------------------------------------- 1 | import { randf } from '@typegpu/noise'; 2 | import tgpu, { type TgpuFn } from 'typegpu'; 3 | import * as d from 'typegpu/data'; 4 | 5 | // #region data structures 6 | 7 | export const VertexOutput = { 8 | position: d.builtin.position, 9 | color: d.vec4f, 10 | isExpired: d.interpolate('flat', d.u32), 11 | }; 12 | 13 | export const ParticleGeometry = d.struct({ 14 | tilt: d.f32, 15 | angle: d.f32, 16 | color: d.vec4f, 17 | }); 18 | 19 | export const ParticleData = d.struct({ 20 | position: d.vec2f, 21 | velocity: d.vec2f, 22 | seed: d.f32, 23 | timeLeft: d.f32, 24 | }); 25 | 26 | // #endregion 27 | 28 | // #region slots 29 | 30 | export const canvasAspectRatio = tgpu['~unstable'].accessor(d.f32); 31 | export const particles = tgpu['~unstable'].accessor(d.arrayOf(ParticleData, 1)); 32 | export const maxDurationTime = tgpu.slot(); 33 | export const initParticle = tgpu.slot d.Void>>(); 34 | export const maxParticleAmount = tgpu.slot(); 35 | export const deltaTime = tgpu['~unstable'].accessor(d.f32); 36 | export const time = tgpu['~unstable'].accessor(d.f32); 37 | export const gravity = tgpu.slot d.Vec2f>>(); 38 | 39 | // #endregion 40 | 41 | // #region functions 42 | 43 | export type GravityFn = (pos: d.v2f) => d.v2f; 44 | export const gravityFn = tgpu.fn([d.vec2f], d.vec2f); 45 | 46 | export type InitParticleFn = (index: number) => void; 47 | export const initParticleFn = tgpu.fn([d.i32]); 48 | 49 | export const rotate = tgpu.fn( 50 | [d.vec2f, d.f32], 51 | d.vec2f, 52 | )(/* wgsl */ `(v: vec2f, angle: f32) -> vec2f { 53 | return vec2( 54 | (v.x * cos(angle)) - (v.y * sin(angle)), 55 | (v.x * sin(angle)) + (v.y * cos(angle)) 56 | ); 57 | }`); 58 | 59 | export const mainVert = tgpu['~unstable'] 60 | .vertexFn({ 61 | in: { 62 | tilt: d.f32, 63 | angle: d.f32, 64 | color: d.vec4f, 65 | center: d.vec2f, 66 | timeLeft: d.f32, 67 | index: d.builtin.vertexIndex, 68 | }, 69 | out: VertexOutput, 70 | })( 71 | /* wgsl */ `{ 72 | let width = in.tilt; 73 | let height = in.tilt / 2; 74 | 75 | if (in.timeLeft < 0.1) { 76 | return Out(vec4f(), vec4f(), 1); 77 | } 78 | 79 | let geometry = array( 80 | vec2f(0, 0), 81 | vec2f(width, 0), 82 | vec2f(0, height), 83 | vec2f(width, height), 84 | ); 85 | var pos = rotate(geometry[in.index] / 350, in.angle) + in.center; 86 | 87 | if (canvasAspectRatio < 1) { 88 | var center = width / 2 / 350; 89 | pos.x -= in.center.x + center; 90 | pos.x /= canvasAspectRatio; 91 | pos.x += in.center.x + center; 92 | } else { 93 | var center = height / 2 / 350; 94 | pos.y -= in.center.y + center; 95 | pos.y *= canvasAspectRatio; 96 | pos.y += in.center.y + center; 97 | } 98 | 99 | let alpha = min(f32(in.timeLeft) / 1000.f, 1); 100 | return Out(vec4f(pos, 0.0, 1.0), alpha * in.color.a * vec4f(in.color.rgb, 1), 0); 101 | }`, 102 | ) 103 | .$uses({ rotate, canvasAspectRatio }); 104 | 105 | export const mainFrag = tgpu['~unstable'].fragmentFn({ 106 | in: VertexOutput, 107 | out: d.vec4f, 108 | })(/* wgsl */ `{ 109 | if (in.isExpired != 0) { 110 | discard; 111 | } 112 | 113 | return in.color; 114 | }`); 115 | 116 | export const mainCompute = tgpu['~unstable'] 117 | .computeFn({ 118 | in: { gid: d.builtin.globalInvocationId }, 119 | workgroupSize: [64], 120 | })( 121 | /* wgsl */ `{ 122 | let index = in.gid.x; 123 | 124 | if particles[index].timeLeft < 0.01 { 125 | return; 126 | } 127 | 128 | let phase = (time / 300) + particles[index].seed; 129 | 130 | particles[index].velocity += gravity(particles[index].position) * deltaTime / 1000; 131 | particles[index].position += particles[index].velocity * deltaTime / 1000 + vec2f(sin(phase) / 600, cos(phase) / 500); 132 | particles[index].timeLeft -= deltaTime; 133 | }`, 134 | ) 135 | .$uses({ gravity, particles, deltaTime, time }); 136 | 137 | export const defaultInitParticle: InitParticleFn = (i) => { 138 | 'use gpu'; 139 | const particle = particles.value[i]; 140 | 141 | particle.position = d.vec2f(randf.sample() * 2 - 1, randf.sample() / 1.5 + 1); 142 | particle.velocity = d.vec2f( 143 | randf.sample() * 2 - 1, 144 | -(randf.sample() / 25 + 0.01) * 50, 145 | ); 146 | 147 | particles.value[i] = particle; 148 | }; 149 | 150 | const preInitParticle = initParticleFn((i) => { 151 | 'use gpu'; 152 | randf.seed2(d.vec2f(d.f32(i), d.f32(time.value % 1111))); 153 | 154 | const particle = particles.value[i]; 155 | particle.timeLeft = maxDurationTime.value * 1000; 156 | particle.seed = randf.sample(); 157 | 158 | particles.value[i] = particle; 159 | }); 160 | 161 | export const initCompute = tgpu['~unstable'].computeFn({ 162 | in: { gid: d.builtin.globalInvocationId }, 163 | workgroupSize: [1], 164 | })((input) => { 165 | preInitParticle(d.i32(input.gid.x)); 166 | initParticle.value(d.i32(input.gid.x)); 167 | }); 168 | 169 | export const addParticleCompute = tgpu['~unstable'] 170 | .computeFn({ 171 | workgroupSize: [1], 172 | })( 173 | /* wgsl */ `{ 174 | for (var i = 0; i < maxParticleAmount; i++) { 175 | if particles[i].timeLeft < 0.1 { 176 | preInitParticle(i); 177 | initParticle(i); 178 | return; 179 | } 180 | } 181 | 182 | var minTimeLeft = particles[0].timeLeft; 183 | var minIndex = 0; 184 | 185 | for (var i = 1; i < maxParticleAmount; i++) { 186 | if particles[i].timeLeft < minTimeLeft { 187 | minTimeLeft = particles[i].timeLeft; 188 | minIndex = i; 189 | } 190 | } 191 | 192 | initParticle(minIndex); 193 | }`, 194 | ) 195 | .$uses({ 196 | particles, 197 | initParticle, 198 | maxParticleAmount, 199 | preInitParticle, 200 | }); 201 | 202 | // #endregion 203 | 204 | // #region layouts 205 | 206 | export const geometryLayout = tgpu.vertexLayout( 207 | d.arrayOf(ParticleGeometry), 208 | 'instance', 209 | ); 210 | 211 | export const dataLayout = tgpu.vertexLayout( 212 | d.arrayOf(ParticleData), 213 | 'instance', 214 | ); 215 | 216 | // #endregion 217 | -------------------------------------------------------------------------------- /apps/test-web-app/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { randf } from '@typegpu/noise'; 2 | import { type ReactNode, useRef, useState } from 'react'; 3 | import * as d from 'typegpu/data'; 4 | import * as std from 'typegpu/std'; 5 | import { 6 | type ConfettiRef, 7 | type GravityFn, 8 | type InitParticleFn, 9 | particles, 10 | } from 'typegpu-confetti'; 11 | import { 12 | Confetti, 13 | ConfettiProvider, 14 | useConfetti, 15 | } from 'typegpu-confetti/react'; 16 | 17 | const centerGravity: GravityFn = (pos) => { 18 | 'use gpu'; 19 | return std.mul(2, d.vec2f(d.f32(-pos.x), d.f32(-pos.y))); 20 | }; 21 | 22 | const rightGravity: GravityFn = () => { 23 | 'use gpu'; 24 | return d.vec2f(2.5, 0); 25 | }; 26 | const upGravity: GravityFn = () => { 27 | 'use gpu'; 28 | return d.vec2f(0, 0.5); 29 | }; 30 | const strongGravity: GravityFn = () => { 31 | 'use gpu'; 32 | return d.vec2f(0, -3); 33 | }; 34 | 35 | const pointInitParticle: InitParticleFn = (index) => { 36 | 'use gpu'; 37 | particles.value[index].position = d.vec2f( 38 | (2 * randf.sample() - 1) / 2 / 50, 39 | (2 * randf.sample() - 1) / 2 / 50, 40 | ); 41 | particles.value[index].velocity = d.vec2f( 42 | 50 * ((randf.sample() * 2 - 1) / 35 / 0.5), 43 | 50 * ((randf.sample() * 2 - 1) / 30 + 0.05), 44 | ); 45 | }; 46 | 47 | const twoSidesInitParticle: InitParticleFn = (i) => { 48 | 'use gpu'; 49 | 50 | if (i % 2 === 0) { 51 | particles.value[i].position = d.vec2f( 52 | (2 * randf.sample() - 1) / 2 / 50 + 1, 53 | (2 * randf.sample() - 1) / 2 / 50, 54 | ); 55 | 56 | particles.value[i].velocity = d.vec2f( 57 | -1 + (randf.sample() * 2 - 1), 58 | 1.5 + (randf.sample() * 2 - 1), 59 | ); 60 | } else { 61 | particles.value[i].position = d.vec2f( 62 | (2 * randf.sample() - 1) / 2 / 50 - 1, 63 | (2 * randf.sample() - 1) / 2 / 50, 64 | ); 65 | 66 | particles.value[i].velocity = d.vec2f( 67 | 1 + (randf.sample() * 2 - 1), 68 | 1.5 + (randf.sample() * 2 - 1), 69 | ); 70 | } 71 | }; 72 | 73 | const customGravity: GravityFn = (pos) => { 74 | 'use gpu'; 75 | return d.vec2f(-pos.x, -3); 76 | }; 77 | 78 | export default function App() { 79 | return ( 80 | 81 |
82 |

typegpu-confetti

83 |
84 | 85 | 86 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 137 | 138 | 139 | 140 | 145 | 146 | 147 | 148 |
149 |
150 |
151 | ); 152 | } 153 | 154 | function ButtonRow({ 155 | icon, 156 | label, 157 | children, 158 | }: { 159 | icon?: string; 160 | label?: string; 161 | children: ReactNode; 162 | }) { 163 | const [confettiKey, setConfettiKey] = useState(0); 164 | return ( 165 | <> 166 | 172 | 173 | {confettiKey > 0 && ( 174 |
187 | {children} 188 |
189 | )} 190 | 191 | ); 192 | } 193 | 194 | function ConfettiContextButton() { 195 | const confetti = useConfetti(); 196 | 197 | return ( 198 | 204 | ); 205 | } 206 | 207 | function ImperativeConfettiButtonRow({ 208 | icon, 209 | label, 210 | }: { 211 | icon?: string; 212 | label?: string; 213 | }) { 214 | const confettiRef = useRef(null); 215 | 216 | return ( 217 | <> 218 | 227 | 228 | 234 | 235 | ); 236 | } 237 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # typegpu-confetti 4 | 5 | `typegpu-confetti` is a package for highly-customizable confetti animations in React Native, computed and rendered exclusively on the GPU. Written using [react-native-wgpu](https://github.com/wcandillon/react-native-webgpu/) and [TypeGPU](https://github.com/software-mansion/TypeGPU). 6 | 7 | 8 | 9 | 10 | ## Installation 11 | 12 | In order to use the package in React Native, you need to install the [react-native-wgpu](https://github.com/wcandillon/react-native-webgpu/) package: 13 | ```sh 14 | npm install react-native-wgpu 15 | ``` 16 | 17 | Please refer to the react-native-wgpu documentation for further information about its installation. Note that the package is not supported by Expo Go, so running `expo prebuild` is required. 18 | 19 | Then to install the `typegpu-confetti` package, run: 20 | ```sh 21 | npm install typegpu-confetti 22 | ``` 23 | 24 | Furthermore, if you want to be able to pass JavaScript functions marked with the "use gpu" directive to the Confetti component, you need to include the [unplugin-typegpu](https://www.npmjs.com/package/unplugin-typegpu) babel plugin in your project. 25 | 26 | ```sh 27 | npm install unplugin-typegpu 28 | ``` 29 | 30 | For further information about the plugin and the overall tgpu functions functionality, please refer to the [TypeGPU documentation](https://docs.swmansion.com/TypeGPU/getting-started/). 31 | 32 | ## Usage 33 | 34 | ### 1. Recommended 35 | 36 | #### `useConfetti` hook 37 | 38 | ```tsx 39 | import { useConfetti } from 'typegpu-confetti/react-native'; 40 | 41 | function SomeInnerComponent() { 42 | const confettiRef = useConfetti(); 43 | 44 | return ( 45 | 46 |