vireglass 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/NOTICE +10 -0
- package/README.md +216 -0
- package/android/build.gradle +15 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/expo/modules/glasslens/GlassBackdropView.kt +335 -0
- package/android/src/main/java/expo/modules/glasslens/GlassLensModule.kt +49 -0
- package/android/src/main/java/expo/modules/glasslens/GlassLensView.kt +463 -0
- package/android/src/main/java/expo/modules/glasslens/GlassProbeView.kt +76 -0
- package/dist/adaptation-BSbbdExK.d.cts +78 -0
- package/dist/adaptation-BSbbdExK.d.ts +78 -0
- package/dist/adapters-BPB0D5nr.d.cts +357 -0
- package/dist/adapters-BgSc-smX.d.ts +357 -0
- package/dist/chunk-HF2AWZEZ.js +1741 -0
- package/dist/chunk-HF2AWZEZ.js.map +1 -0
- package/dist/chunk-JNRZNMJJ.js +20 -0
- package/dist/chunk-JNRZNMJJ.js.map +1 -0
- package/dist/index.cjs +2159 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +294 -0
- package/dist/index.d.ts +294 -0
- package/dist/index.js +286 -0
- package/dist/index.js.map +1 -0
- package/dist/material-CkUHUBoy.d.cts +408 -0
- package/dist/material-CkUHUBoy.d.ts +408 -0
- package/dist/react.cjs +73 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +25 -0
- package/dist/react.d.ts +25 -0
- package/dist/react.js +59 -0
- package/dist/react.js.map +1 -0
- package/dist/web/index.cjs +1990 -0
- package/dist/web/index.cjs.map +1 -0
- package/dist/web/index.d.cts +148 -0
- package/dist/web/index.d.ts +148 -0
- package/dist/web/index.js +657 -0
- package/dist/web/index.js.map +1 -0
- package/expo-module.config.json +6 -0
- package/package.json +127 -0
- package/src/accessibility.ts +51 -0
- package/src/adaptation.ts +85 -0
- package/src/adapters.ts +362 -0
- package/src/concentric.ts +25 -0
- package/src/geometry.ts +151 -0
- package/src/glass-scale.ts +37 -0
- package/src/group-model.ts +224 -0
- package/src/index.ts +17 -0
- package/src/lens-shader.ts +547 -0
- package/src/material.ts +553 -0
- package/src/native/environment.ts +70 -0
- package/src/native/glass-group.tsx +107 -0
- package/src/native/glass-ink.tsx +44 -0
- package/src/native/glass-surface.tsx +477 -0
- package/src/native/index.ts +18 -0
- package/src/native/lens.ts +106 -0
- package/src/native/provider.tsx +58 -0
- package/src/native/surface-registry.ts +51 -0
- package/src/native/system-accessibility.ts +90 -0
- package/src/optics.ts +177 -0
- package/src/react.ts +102 -0
- package/src/reference-scene.ts +222 -0
- package/src/scroll-edge.ts +30 -0
- package/src/sdf.ts +149 -0
- package/src/surface-shader.ts +287 -0
- package/src/targets/agsl.ts +8 -0
- package/src/targets/glsl.ts +140 -0
- package/src/touch-response.ts +195 -0
- package/src/web/gl.ts +119 -0
- package/src/web/index.ts +3 -0
- package/src/web/probe.ts +312 -0
- package/src/web/reference-draw.ts +142 -0
- package/src/web/renderer.ts +524 -0
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
package expo.modules.glasslens
|
|
2
|
+
|
|
3
|
+
import android.annotation.SuppressLint
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.graphics.Canvas
|
|
6
|
+
import android.graphics.HardwareRenderer
|
|
7
|
+
import android.graphics.PixelFormat
|
|
8
|
+
import android.graphics.RenderNode
|
|
9
|
+
import android.graphics.drawable.ColorDrawable
|
|
10
|
+
import android.hardware.HardwareBuffer
|
|
11
|
+
import android.media.ImageReader
|
|
12
|
+
import android.os.Build
|
|
13
|
+
import android.os.Handler
|
|
14
|
+
import android.os.HandlerThread
|
|
15
|
+
import android.os.Looper
|
|
16
|
+
import android.util.Log
|
|
17
|
+
import android.view.View
|
|
18
|
+
import android.view.ViewTreeObserver
|
|
19
|
+
import expo.modules.kotlin.AppContext
|
|
20
|
+
import expo.modules.kotlin.views.ExpoView
|
|
21
|
+
import java.util.Collections
|
|
22
|
+
import java.util.WeakHashMap
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Backdrop capture for the lens: screen content is written into a `RenderNode` WITHOUT blur.
|
|
26
|
+
*
|
|
27
|
+
* `BlurView` from expo-blur used to play this role, and it produced a dithered copy. Measured in
|
|
28
|
+
* the lab: a one-to-one sample from its capture gives noise of 6.4–8.0 against 0.000 on the same
|
|
29
|
+
* screen area right next to it. The cause is the path itself: on Android 13+ the library routes
|
|
30
|
+
* the capture through `RenderEffect.createBlurEffect`, and Skia dithers the blur's output to
|
|
31
|
+
* hide banding. At zero radius there's no blur, but the dithering stays. There's no blur here at
|
|
32
|
+
* all.
|
|
33
|
+
*
|
|
34
|
+
* Four things this implementation has already broken on — keep them in mind when editing:
|
|
35
|
+
*
|
|
36
|
+
* 1. The write must happen from `dispatchDraw`. `onPreDraw` doesn't yet have a valid drawing
|
|
37
|
+
* context, and `super.dispatchDraw` into a separately created canvas produces EMPTINESS.
|
|
38
|
+
* 2. The node can't be drawn onto the same canvas that just recorded it: the screen gets a stale
|
|
39
|
+
* frame, and the scene looks frozen even though the animation is running. Children are drawn
|
|
40
|
+
* directly; the node is handed only to the lens.
|
|
41
|
+
* 3. A re-record has to be requested every frame from `onPreDraw`: this view's own display list
|
|
42
|
+
* doesn't rebuild itself when only its children change — it just references their nodes.
|
|
43
|
+
* 4. `setPosition` is mandatory. A node is clipped by its own bounds, and a freshly created one
|
|
44
|
+
* has EMPTY bounds: `beginRecording(w, h)` only sets the recording's size, not its bounds.
|
|
45
|
+
* Without this, `drawRenderNode` draws nothing, `content.eval` in the lens returns zero
|
|
46
|
+
* alpha, and the glass turns transparent — while reading as "clean" on any noise metric,
|
|
47
|
+
* because there's nothing there for it to show (exactly the E-27 trap in material-lab.md).
|
|
48
|
+
*/
|
|
49
|
+
@SuppressLint("ViewConstructor")
|
|
50
|
+
class GlassBackdropView(context: Context, appContext: AppContext) : ExpoView(context, appContext) {
|
|
51
|
+
private val node: RenderNode? =
|
|
52
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) RenderNode("vireglass-backdrop") else null
|
|
53
|
+
|
|
54
|
+
// Weak references: the lens leaves the screen before the backdrop does, and holding onto it
|
|
55
|
+
// here would mean holding its whole subtree.
|
|
56
|
+
private val lenses = Collections.newSetFromMap(WeakHashMap<GlassLensView, Boolean>())
|
|
57
|
+
|
|
58
|
+
private var capturing = false
|
|
59
|
+
|
|
60
|
+
private val onPreDraw = ViewTreeObserver.OnPreDrawListener {
|
|
61
|
+
if (lenses.isNotEmpty()) invalidate()
|
|
62
|
+
true
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** The frame's snapshot, or null before the first recording. */
|
|
66
|
+
val content: RenderNode?
|
|
67
|
+
get() = node?.takeIf { it.hasDisplayList() }
|
|
68
|
+
|
|
69
|
+
fun register(lens: GlassLensView) {
|
|
70
|
+
if (lenses.add(lens)) invalidate()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
fun unregister(lens: GlassLensView) {
|
|
74
|
+
lenses.remove(lens)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
override fun dispatchDraw(canvas: Canvas) {
|
|
78
|
+
val n = node
|
|
79
|
+
if (n == null || capturing || lenses.isEmpty() ||
|
|
80
|
+
!canvas.isHardwareAccelerated || width <= 0 || height <= 0
|
|
81
|
+
) {
|
|
82
|
+
super.dispatchDraw(canvas)
|
|
83
|
+
return
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
capturing = true
|
|
87
|
+
try {
|
|
88
|
+
n.setPosition(0, 0, width, height)
|
|
89
|
+
val recording = n.beginRecording(width, height)
|
|
90
|
+
try {
|
|
91
|
+
// The screen's background comes from a style on the PARENT view, and only our children
|
|
92
|
+
// land in the node. Without the fill, the lens over an unpainted spot would sample
|
|
93
|
+
// transparency instead of a backdrop.
|
|
94
|
+
(parent as? View)?.background?.let { if (it is ColorDrawable) recording.drawColor(it.color) }
|
|
95
|
+
super.dispatchDraw(recording)
|
|
96
|
+
} finally {
|
|
97
|
+
n.endRecording()
|
|
98
|
+
}
|
|
99
|
+
} finally {
|
|
100
|
+
capturing = false
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
super.dispatchDraw(canvas)
|
|
104
|
+
|
|
105
|
+
// The lens rebuilds its own display list: it holds the offset to the backdrop, and that
|
|
106
|
+
// changes as the glass moves. The node's content is picked up by reference even without this.
|
|
107
|
+
for (lens in lenses) lens.invalidate()
|
|
108
|
+
|
|
109
|
+
scheduleProbe()
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
override fun onAttachedToWindow() {
|
|
113
|
+
super.onAttachedToWindow()
|
|
114
|
+
viewTreeObserver.addOnPreDrawListener(onPreDraw)
|
|
115
|
+
// The failure latch is only cleared here, on the view's new lifetime: resetting it inside
|
|
116
|
+
// `releaseProbe` would mean recreating the thread and the renderer every 180 ms on a stable
|
|
117
|
+
// failure.
|
|
118
|
+
probeFailed = false
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
override fun onDetachedFromWindow() {
|
|
122
|
+
viewTreeObserver.removeOnPreDrawListener(onPreDraw)
|
|
123
|
+
// The lens set is NOT cleared: the references are weak, and the backdrop can reconnect to a
|
|
124
|
+
// window, in which case the lenses would otherwise be left without updates forever.
|
|
125
|
+
node?.discardDisplayList()
|
|
126
|
+
releaseProbe()
|
|
127
|
+
super.onDetachedFromWindow()
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
131
|
+
// LIGHTNESS PROBE
|
|
132
|
+
//
|
|
133
|
+
// The glass can adjust its own body to the backdrop, but there's a limit: when the backdrop is
|
|
134
|
+
// light AND the ink over it is light, no amount of body density will separate them by
|
|
135
|
+
// lightness anymore. Past that point the decision belongs not to the glass but to the APP — it
|
|
136
|
+
// recolors the ink. For that, the app needs to know the backdrop's lightness UNDER the glass,
|
|
137
|
+
// and only this view knows it.
|
|
138
|
+
//
|
|
139
|
+
// We don't read from the screen (the glass itself is already drawn there — that would create
|
|
140
|
+
// feedback) but from our own capture node: it holds exactly the backdrop and nothing else. The
|
|
141
|
+
// node is downscaled to a 16×32 grid and rendered into an `ImageReader` via a
|
|
142
|
+
// `HardwareRenderer` — the same mechanism Compose uses to snapshot its own layers. Once every
|
|
143
|
+
// PROBE_INTERVAL_MS, so it never enters the frame budget.
|
|
144
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
145
|
+
|
|
146
|
+
private var probeNode: RenderNode? = null
|
|
147
|
+
private var probeReader: ImageReader? = null
|
|
148
|
+
private var probeRenderer: HardwareRenderer? = null
|
|
149
|
+
private var probeThread: HandlerThread? = null
|
|
150
|
+
private var probeHandler: Handler? = null
|
|
151
|
+
private var probeFailed = false
|
|
152
|
+
private var lastProbeAt = 0L
|
|
153
|
+
private var probePending = false
|
|
154
|
+
|
|
155
|
+
/** Lightness grid of the latest snapshot, PROBE_W×PROBE_H, values 0..1. */
|
|
156
|
+
@Volatile
|
|
157
|
+
var lumaGrid: FloatArray? = null
|
|
158
|
+
private set
|
|
159
|
+
|
|
160
|
+
/** Average color of the same snapshot, per cell: the lens tints the rim with the content's
|
|
161
|
+
* color. */
|
|
162
|
+
@Volatile
|
|
163
|
+
var colorGrid: FloatArray? = null
|
|
164
|
+
private set
|
|
165
|
+
|
|
166
|
+
private val mainHandler = Handler(Looper.getMainLooper())
|
|
167
|
+
|
|
168
|
+
private fun scheduleProbe() {
|
|
169
|
+
if (probeFailed || Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) return
|
|
170
|
+
if (probePending) return
|
|
171
|
+
val now = android.os.SystemClock.uptimeMillis()
|
|
172
|
+
if (now - lastProbeAt < PROBE_INTERVAL_MS) return
|
|
173
|
+
lastProbeAt = now
|
|
174
|
+
probePending = true
|
|
175
|
+
// Not from inside the draw pass: `syncAndDraw` synchronizes the node tree, and we're right
|
|
176
|
+
// in the middle of traversing it. `post` schedules the render right after the frame.
|
|
177
|
+
post { renderProbe() }
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private fun renderProbe() {
|
|
181
|
+
probePending = false
|
|
182
|
+
val n = content ?: return
|
|
183
|
+
if (width <= 0 || height <= 0) return
|
|
184
|
+
try {
|
|
185
|
+
ensureProbe() ?: return
|
|
186
|
+
val pn = probeNode ?: return
|
|
187
|
+
pn.setPosition(0, 0, SHOT_W, SHOT_H)
|
|
188
|
+
val c = pn.beginRecording(SHOT_W, SHOT_H)
|
|
189
|
+
try {
|
|
190
|
+
c.scale(SHOT_W.toFloat() / width, SHOT_H.toFloat() / height)
|
|
191
|
+
c.drawRenderNode(n)
|
|
192
|
+
} finally {
|
|
193
|
+
pn.endRecording()
|
|
194
|
+
}
|
|
195
|
+
probeRenderer?.createRenderRequest()?.setWaitForPresent(false)?.syncAndDraw()
|
|
196
|
+
} catch (e: Throwable) {
|
|
197
|
+
// The probe isn't a required part: without it, the glass only loses theme auto-inversion.
|
|
198
|
+
Log.w("GlassLens", "lightness probe disabled", e)
|
|
199
|
+
probeFailed = true
|
|
200
|
+
releaseProbe()
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private fun ensureProbe(): HardwareRenderer? {
|
|
205
|
+
probeRenderer?.let { return it }
|
|
206
|
+
val thread = HandlerThread("vireglass-probe").apply { start() }
|
|
207
|
+
probeThread = thread
|
|
208
|
+
val handler = Handler(thread.looper)
|
|
209
|
+
probeHandler = handler
|
|
210
|
+
val reader = ImageReader.newInstance(
|
|
211
|
+
SHOT_W,
|
|
212
|
+
SHOT_H,
|
|
213
|
+
PixelFormat.RGBA_8888,
|
|
214
|
+
2,
|
|
215
|
+
HardwareBuffer.USAGE_GPU_COLOR_OUTPUT or HardwareBuffer.USAGE_CPU_READ_OFTEN,
|
|
216
|
+
)
|
|
217
|
+
reader.setOnImageAvailableListener({ r -> readProbe(r) }, handler)
|
|
218
|
+
probeReader = reader
|
|
219
|
+
val pn = RenderNode("vireglass-probe")
|
|
220
|
+
probeNode = pn
|
|
221
|
+
val renderer = HardwareRenderer().apply {
|
|
222
|
+
setSurface(reader.surface)
|
|
223
|
+
setContentRoot(pn)
|
|
224
|
+
}
|
|
225
|
+
probeRenderer = renderer
|
|
226
|
+
return renderer
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
private fun readProbe(reader: ImageReader) {
|
|
230
|
+
val image = try {
|
|
231
|
+
reader.acquireLatestImage()
|
|
232
|
+
} catch (e: Throwable) {
|
|
233
|
+
Log.w("GlassLens", "probe frame wasn't delivered", e)
|
|
234
|
+
null
|
|
235
|
+
} ?: return
|
|
236
|
+
try {
|
|
237
|
+
val plane = image.planes[0]
|
|
238
|
+
val buf = plane.buffer
|
|
239
|
+
val rowStride = plane.rowStride
|
|
240
|
+
val pixelStride = plane.pixelStride
|
|
241
|
+
val luma = FloatArray(PROBE_W * PROBE_H)
|
|
242
|
+
val color = FloatArray(PROBE_W * PROBE_H * 3)
|
|
243
|
+
val cells = (SHOT_BLOCK * SHOT_BLOCK).toFloat()
|
|
244
|
+
for (y in 0 until PROBE_H) {
|
|
245
|
+
for (x in 0 until PROBE_W) {
|
|
246
|
+
var r = 0f
|
|
247
|
+
var g = 0f
|
|
248
|
+
var b = 0f
|
|
249
|
+
for (dy in 0 until SHOT_BLOCK) {
|
|
250
|
+
for (dx in 0 until SHOT_BLOCK) {
|
|
251
|
+
val o = (y * SHOT_BLOCK + dy) * rowStride + (x * SHOT_BLOCK + dx) * pixelStride
|
|
252
|
+
r += (buf.get(o).toInt() and 0xFF) / 255f
|
|
253
|
+
g += (buf.get(o + 1).toInt() and 0xFF) / 255f
|
|
254
|
+
b += (buf.get(o + 2).toInt() and 0xFF) / 255f
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
r /= cells
|
|
258
|
+
g /= cells
|
|
259
|
+
b /= cells
|
|
260
|
+
val i = y * PROBE_W + x
|
|
261
|
+
luma[i] = 0.2126f * r + 0.7152f * g + 0.0722f * b
|
|
262
|
+
color[i * 3] = r
|
|
263
|
+
color[i * 3 + 1] = g
|
|
264
|
+
color[i * 3 + 2] = b
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
lumaGrid = luma
|
|
268
|
+
colorGrid = color
|
|
269
|
+
} catch (e: Throwable) {
|
|
270
|
+
Log.w("GlassLens", "probe snapshot failed to read", e)
|
|
271
|
+
} finally {
|
|
272
|
+
// The frame closes on the probe thread and can arrive late: `releaseProbe` from the main
|
|
273
|
+
// thread may have already closed the reader, in which case `close()` throws
|
|
274
|
+
// `IllegalStateException`.
|
|
275
|
+
try {
|
|
276
|
+
image.close()
|
|
277
|
+
} catch (e: Throwable) {
|
|
278
|
+
Log.w("GlassLens", "probe frame failed to close", e)
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
mainHandler.post { for (lens in lenses) lens.onBackdropProbed() }
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
private fun releaseProbe() {
|
|
285
|
+
probeRenderer?.let {
|
|
286
|
+
it.stop()
|
|
287
|
+
// The surface is detached BEFORE destroy: otherwise the runtime logs "A resource failed to
|
|
288
|
+
// call Surface.release" on every recreated probe.
|
|
289
|
+
it.setSurface(null)
|
|
290
|
+
it.destroy()
|
|
291
|
+
}
|
|
292
|
+
probeRenderer = null
|
|
293
|
+
probeReader?.close()
|
|
294
|
+
probeReader = null
|
|
295
|
+
probeNode?.discardDisplayList()
|
|
296
|
+
probeNode = null
|
|
297
|
+
probeThread?.quitSafely()
|
|
298
|
+
probeThread = null
|
|
299
|
+
probeHandler = null
|
|
300
|
+
lumaGrid = null
|
|
301
|
+
colorGrid = null
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
companion object {
|
|
305
|
+
/**
|
|
306
|
+
* The probe's grid. A cell must be SMALLER than the finest texture that needs telling apart:
|
|
307
|
+
* at 16×32 a cell came out to 67 pixels, and an 8 dp checkerboard averaged out to flat gray —
|
|
308
|
+
* the probe honestly reported "the backdrop is uniform," while ink over the glass drowned in
|
|
309
|
+
* the cells. At 48×96 a cell is about 22 pixels, i.e. the size of the product's finest
|
|
310
|
+
* texture. The breakdown runs on a background thread once every PROBE_INTERVAL_MS, so it
|
|
311
|
+
* never enters the frame budget.
|
|
312
|
+
*/
|
|
313
|
+
const val PROBE_W = 48
|
|
314
|
+
const val PROBE_H = 96
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* The snapshot is taken LARGER than the grid, and a cell is then averaged over a
|
|
318
|
+
* SHOT_BLOCK×SHOT_BLOCK block.
|
|
319
|
+
*
|
|
320
|
+
* Downscaling the screen straight to the grid doesn't work: at a twenty-five-fold reduction
|
|
321
|
+
* the rasterizer doesn't average, it filters — at the 0.50 → 0.69 step a cell came out to
|
|
322
|
+
* 0.486, i.e. below BOTH levels. On flat fields this goes unnoticed, but on a gradient the
|
|
323
|
+
* probe used to underestimate the lightness slope by almost a third, and the element's body
|
|
324
|
+
* on Android tracked the backdrop more closely than on the web (issue #113). The web doesn't
|
|
325
|
+
* make this mistake: there, a shader computes the cell by averaging a 5×5 block.
|
|
326
|
+
*/
|
|
327
|
+
const val SHOT_BLOCK = 5
|
|
328
|
+
const val SHOT_W = PROBE_W * SHOT_BLOCK
|
|
329
|
+
const val SHOT_H = PROBE_H * SHOT_BLOCK
|
|
330
|
+
|
|
331
|
+
/** Snapshot rate. The adaptation travels over roughly half a second regardless, so there's
|
|
332
|
+
* no point going faster, and this shouldn't touch the frame budget either way. */
|
|
333
|
+
const val PROBE_INTERVAL_MS = 180L
|
|
334
|
+
}
|
|
335
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
package expo.modules.glasslens
|
|
2
|
+
|
|
3
|
+
import android.os.Build
|
|
4
|
+
import expo.modules.kotlin.modules.Module
|
|
5
|
+
import expo.modules.kotlin.modules.ModuleDefinition
|
|
6
|
+
|
|
7
|
+
class GlassLensModule : Module() {
|
|
8
|
+
override fun definition() = ModuleDefinition {
|
|
9
|
+
Name("GlassLens")
|
|
10
|
+
|
|
11
|
+
// `createRuntimeShaderEffect` arrived in Android 13. Below it, the view works as an ordinary
|
|
12
|
+
// container, and JS keeps the old affine magnification as a fallback.
|
|
13
|
+
Constants("isSupported" to (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU))
|
|
14
|
+
|
|
15
|
+
View(GlassLensView::class) {
|
|
16
|
+
Name("GlassLensView")
|
|
17
|
+
|
|
18
|
+
// Lightness of the backdrop under the glass. An event, not a prop: the app has to learn
|
|
19
|
+
// the limit past which the glass can no longer separate itself from the ink, and recolor
|
|
20
|
+
// the ink itself.
|
|
21
|
+
Events("onBackdropSample")
|
|
22
|
+
|
|
23
|
+
Prop("backdropId") { view: GlassLensView, value: Int? -> view.backdropId = value }
|
|
24
|
+
Prop("shaderSource") { view: GlassLensView, value: String -> view.shaderSource = value }
|
|
25
|
+
Prop("glassWidth") { view: GlassLensView, value: Float -> view.glassWidth = value }
|
|
26
|
+
Prop("glassHeight") { view: GlassLensView, value: Float -> view.glassHeight = value }
|
|
27
|
+
// The whole material as one channel: name ↔ size ↔ value arrive together, so drifting out
|
|
28
|
+
// of sync with the shader silently is no longer possible (see `applyChannel`).
|
|
29
|
+
Prop("uniformNames") { view: GlassLensView, value: List<String> -> view.uniformNames = value }
|
|
30
|
+
Prop("uniformValues") { view: GlassLensView, value: List<Double> -> view.uniformValues = value }
|
|
31
|
+
Prop("uniformSizes") { view: GlassLensView, value: List<Int> -> view.uniformSizes = value }
|
|
32
|
+
|
|
33
|
+
OnViewDidUpdateProps { view: GlassLensView -> view.applyEffect() }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Backdrop capture: writes its children into a RenderNode, the lens takes it as content.
|
|
37
|
+
View(GlassBackdropView::class) {
|
|
38
|
+
Name("GlassBackdropView")
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Diagnostic probe (docs/adr-001-rendering.md): shows which pixels actually reach
|
|
42
|
+
// `createRuntimeShaderEffect`. Not used in production UI.
|
|
43
|
+
View(GlassProbeView::class) {
|
|
44
|
+
Name("GlassProbeView")
|
|
45
|
+
Prop("mode") { view: GlassProbeView, value: Float -> view.mode = value }
|
|
46
|
+
OnViewDidUpdateProps { view: GlassProbeView -> view.applyEffect() }
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|