lecodes-viewer 0.20.0 → 0.20.2
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/dist-embed/VERSION.json +3 -3
- package/dist-embed/assets/{createViewerLite-DkM-O9BQ.js → createViewerLite-B91Sj6p_.js} +22 -22
- package/dist-embed/assets/{host-Czj6Nu-U.js → host-DZ6302Ho.js} +2 -2
- package/dist-embed/assets/{index-mhLEVWyK.js → index-D10DoNfS.js} +1 -1
- package/dist-embed/assets/{particles-DxYDmI1O.filamat → particles-Cgp1L6RQ.filamat} +0 -0
- package/dist-embed/assets/{particles-quad-DwYo9yCK.filamat → particles-quad-CgrEJN4M.filamat} +0 -0
- package/dist-embed/embed.js +9 -9
- package/package.json +3 -3
- package/src/createViewer.ts +2 -0
- package/src/ui/creator-ui.wasm +0 -0
- package/src/ui/vListSystem.ts +211 -211
- package/src/utils/imageNode.ts +5 -0
- package/src/viewer/createViewer.ts +26 -2
- package/src/viewer/creator-gl.wasm +0 -0
- package/src/viewer/particles-quad.filamat +0 -0
- package/src/viewer/particles.filamat +0 -0
- package/src/viewer2d/creator2d.wasm +0 -0
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lecodes-viewer",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.2",
|
|
4
4
|
"main": "src/createViewer.ts",
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"lecodes-renderer": "0.20.
|
|
6
|
+
"lecodes-renderer": "0.20.2",
|
|
7
7
|
"ogl": "^1.0.11",
|
|
8
|
-
"lecodes-viewer-lite": "0.20.
|
|
8
|
+
"lecodes-viewer-lite": "0.20.2"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
11
|
"dev": "vite",
|
package/src/createViewer.ts
CHANGED
|
@@ -18,6 +18,8 @@ export { getBuffer } from './utils/fetch'
|
|
|
18
18
|
export { decodeKtx2Image, isKtx2Bytes } from 'lecodes-viewer-lite/src/gl/decoders'
|
|
19
19
|
export type { CreatorMethods, EngineMode, Host, HostOptions, Run, RunOptions, UIFactory } from './host'
|
|
20
20
|
export type { PreviewError, MappedFrame } from './sourcemapError'
|
|
21
|
+
// The render tree the canvas UI hands back (`_creatorUI.renderTree()`) — the embedder's verify gates walk it.
|
|
22
|
+
export type { RenderNode } from 'lecodes-renderer'
|
|
21
23
|
export { createCanvasUIRenderer } from './ui/mountCanvasUI'
|
|
22
24
|
export { createBareUI } from './ui/createBareUI'
|
|
23
25
|
export type { SafeAreaEdge, SafeAreaSpec } from './ui/safeAreaSpec'
|
package/src/ui/creator-ui.wasm
CHANGED
|
File without changes
|
package/src/ui/vListSystem.ts
CHANGED
|
@@ -1,212 +1,212 @@
|
|
|
1
|
-
import type { UIScreen } from './UIScreen'
|
|
2
|
-
|
|
3
|
-
type MountedItem = {
|
|
4
|
-
rootPtr: number // detached yoga root айтема
|
|
5
|
-
el: HTMLElement // DOM-корень поддерева
|
|
6
|
-
jsSubtree: any // JS-объект поддерева (для null _id при unmount)
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export type VListInstance = {
|
|
10
|
-
nodeId: number
|
|
11
|
-
el: HTMLElement
|
|
12
|
-
jsNode: any // VListElement
|
|
13
|
-
screen: UIScreen
|
|
14
|
-
pendingScroll: number | null
|
|
15
|
-
items: Map<string, MountedItem>
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export const createVListSystem = (module: any) => {
|
|
19
|
-
|
|
20
|
-
const instances = new Map<number, VListInstance>() // yogaId vlist-ноды → instance
|
|
21
|
-
|
|
22
|
-
// ---------- marshaling ----------
|
|
23
|
-
|
|
24
|
-
const withKey = <R>(key: string, fn: (ptr: number) => R): R => {
|
|
25
|
-
const len = module.lengthBytesUTF8(key) + 1
|
|
26
|
-
const p = module._malloc(len)
|
|
27
|
-
module.stringToUTF8(key, p, len)
|
|
28
|
-
try { return fn(p) } finally { module._free(p) }
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const withStringArray = <R>(arr: string[], fn: (arrPtr: number) => R): R => {
|
|
32
|
-
const ptrs = arr.map(s => {
|
|
33
|
-
const len = module.lengthBytesUTF8(s) + 1
|
|
34
|
-
const p = module._malloc(len)
|
|
35
|
-
module.stringToUTF8(s, p, len)
|
|
36
|
-
return p
|
|
37
|
-
})
|
|
38
|
-
const arrPtr = module._malloc(Math.max(4 * ptrs.length, 4))
|
|
39
|
-
ptrs.forEach((p, i) => { module.HEAPU32[(arrPtr >> 2) + i] = p })
|
|
40
|
-
try { return fn(arrPtr) } finally {
|
|
41
|
-
ptrs.forEach(p => module._free(p))
|
|
42
|
-
module._free(arrPtr)
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const withFloatArray = <R>(arr: number[], fn: (ptr: number) => R): R => {
|
|
47
|
-
const p = module._malloc(Math.max(4 * arr.length, 4))
|
|
48
|
-
arr.forEach((v, i) => { module.HEAPF32[(p >> 2) + i] = v })
|
|
49
|
-
try { return fn(p) } finally { module._free(p) }
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const readStringArray = (arrPtr: number, count: number): string[] => {
|
|
53
|
-
const out: string[] = []
|
|
54
|
-
for (let i = 0; i < count; i++) {
|
|
55
|
-
out.push(module.UTF8ToString(module.HEAPU32[(arrPtr >> 2) + i]))
|
|
56
|
-
}
|
|
57
|
-
return out
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// ---------- layout-проход по айтемам ----------
|
|
61
|
-
|
|
62
|
-
const refresh = (inst: VListInstance) => {
|
|
63
|
-
for (const [key, item] of inst.items) {
|
|
64
|
-
inst.screen.updateSizesFor(item.el)
|
|
65
|
-
item.el.style.top = withKey(key, p => module._vlistOffsetOf(inst.nodeId, p)) + "px"
|
|
66
|
-
item.el.style.left = "0px"
|
|
67
|
-
}
|
|
68
|
-
const h = module._getContentHeight(inst.nodeId)
|
|
69
|
-
inst.el.style.setProperty("--scroll-height", h + "px")
|
|
70
|
-
inst.el.style.setProperty("--scroll-width", "0px")
|
|
71
|
-
|
|
72
|
-
if (inst.pendingScroll !== null) {
|
|
73
|
-
if (Math.abs(inst.el.scrollTop - inst.pendingScroll) >= 1) {
|
|
74
|
-
inst.el.scrollTop = inst.pendingScroll // высота уже выставлена — кламп не сработает
|
|
75
|
-
}
|
|
76
|
-
inst.pendingScroll = null
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// ---------- колбэки из C++ (регистрируются один раз) ----------
|
|
81
|
-
|
|
82
|
-
// g_vlSync
|
|
83
|
-
const syncFunc = module.addFunction(
|
|
84
|
-
(_screenId: number, nodePtr: number, unmountArr: number, unmountCount: number, mountArr: number, mountCount: number) => {
|
|
85
|
-
const inst = instances.get(nodePtr)
|
|
86
|
-
if (!inst) return
|
|
87
|
-
const unmountKeys = readStringArray(unmountArr, unmountCount)
|
|
88
|
-
const mountKeys = readStringArray(mountArr, mountCount)
|
|
89
|
-
|
|
90
|
-
// Контракт: интеграционный слой сносит поддеревья ДО вызова JS _syncWindow
|
|
91
|
-
for (const key of unmountKeys) {
|
|
92
|
-
const item = inst.items.get(key)
|
|
93
|
-
if (!item) continue
|
|
94
|
-
inst.items.delete(key)
|
|
95
|
-
item.el.remove()
|
|
96
|
-
inst.screen.releaseSubtree(item.jsSubtree) // nodeMap-чистка + _id = 0
|
|
97
|
-
withKey(key, p => module._vlistItemUnmounted(nodePtr, p)) // free yoga root
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
inst.jsNode._syncWindow(unmountKeys, mountKeys) // mount: render() → vlistMount()
|
|
101
|
-
refresh(inst)
|
|
102
|
-
}, "viiiiii")
|
|
103
|
-
|
|
104
|
-
// g_vlAdjust — якорение / кламп
|
|
105
|
-
const adjustFunc = module.addFunction((_screenId: number, nodePtr: number, scrollY: number) => {
|
|
106
|
-
const inst = instances.get(nodePtr)
|
|
107
|
-
if (!inst) return
|
|
108
|
-
inst.pendingScroll = scrollY // применим в refresh, после высоты
|
|
109
|
-
}, "viif")
|
|
110
|
-
|
|
111
|
-
// g_vlEdge
|
|
112
|
-
const edgeFunc = module.addFunction((_screenId: number, nodePtr: number, endEdge: number) => {
|
|
113
|
-
const inst = instances.get(nodePtr)
|
|
114
|
-
if (!inst) return
|
|
115
|
-
const listeners = endEdge ? (inst.jsNode.erl ?? []) : (inst.jsNode.strl ?? [])
|
|
116
|
-
for (const cb of listeners) cb()
|
|
117
|
-
}, "viii")
|
|
118
|
-
|
|
119
|
-
module._setVListHandlers(syncFunc, adjustFunc, edgeFunc)
|
|
120
|
-
|
|
121
|
-
// ---------- создание (из UIScreen.addNodeRecursive для type === "vlist") ----------
|
|
122
|
-
|
|
123
|
-
const register = (screen: UIScreen, jsNode: any, el: HTMLElement, yogaId: number) => {
|
|
124
|
-
const inst: VListInstance = { nodeId: yogaId, el, jsNode, screen, items: new Map(), pendingScroll: null }
|
|
125
|
-
instances.set(yogaId, inst)
|
|
126
|
-
|
|
127
|
-
module._vlistInit(yogaId, jsNode._overscan, jsNode._inverted ? 1 : 0,
|
|
128
|
-
jsNode._startThreshold, jsNode._endThreshold)
|
|
129
|
-
|
|
130
|
-
if (jsNode._keys.length > 0) {
|
|
131
|
-
setKeysInternal(yogaId, jsNode._keys, jsNode._estimates)
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
el.addEventListener("scroll", () => {
|
|
135
|
-
module._vlistOnScroll(yogaId, el.scrollTop)
|
|
136
|
-
refresh(inst)
|
|
137
|
-
for (const cb of jsNode.sl ?? []) cb(el.scrollTop)
|
|
138
|
-
})
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Вызывается из UIScreen.updateSizes для vlist-элемента (после выставления его размеров)
|
|
142
|
-
const updateLayout = (yogaId: number) => {
|
|
143
|
-
const inst = instances.get(yogaId)
|
|
144
|
-
if (!inst) return
|
|
145
|
-
module._vlistUpdateLayout(yogaId) // внутри может дернуть sync → mount
|
|
146
|
-
refresh(inst)
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const unregister = (yogaId: number) => {
|
|
150
|
-
const inst = instances.get(yogaId)
|
|
151
|
-
if (!inst) return
|
|
152
|
-
module._vlistFree(yogaId) // освобождает detached-корни
|
|
153
|
-
instances.delete(yogaId)
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const setKeysInternal = (yogaId: number, keys: string[], estimates: number[]) => {
|
|
157
|
-
withStringArray(keys, kp =>
|
|
158
|
-
withFloatArray(estimates, ep =>
|
|
159
|
-
module._vlistSetKeys(yogaId, kp, ep, keys.length)))
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// ---------- API для _creatorUI (зовётся из VListElement) ----------
|
|
163
|
-
|
|
164
|
-
const api = {
|
|
165
|
-
vlistSetKeys: (jsNode: any, keys: string[], estimates: number[]) => {
|
|
166
|
-
setKeysInternal(jsNode._id, keys, estimates)
|
|
167
|
-
},
|
|
168
|
-
vlistInsertKeys: (jsNode: any, index: number, keys: string[], estimates: number[]) => {
|
|
169
|
-
withStringArray(keys, kp =>
|
|
170
|
-
withFloatArray(estimates, ep =>
|
|
171
|
-
module._vlistInsertKeys(jsNode._id, index, kp, ep, keys.length)))
|
|
172
|
-
},
|
|
173
|
-
vlistRemoveKeys: (jsNode: any, keys: string[]) => {
|
|
174
|
-
withStringArray(keys, kp => module._vlistRemoveKeys(jsNode._id, kp, keys.length))
|
|
175
|
-
},
|
|
176
|
-
vlistInvalidate: (jsNode: any, key: string) => {
|
|
177
|
-
withKey(key, p => module._vlistInvalidate(jsNode._id, p))
|
|
178
|
-
},
|
|
179
|
-
// Вызывается из _syncWindow на каждый mount-ключ
|
|
180
|
-
vlistMount: (jsNode: any, key: string, subtree: any) => {
|
|
181
|
-
const inst = instances.get(jsNode._id)
|
|
182
|
-
if (!inst) return
|
|
183
|
-
const rootPtr = withKey(key, p => module._vlistCreateItemRoot(jsNode._id, p))
|
|
184
|
-
const el = inst.screen.buildSubtree(subtree, rootPtr) // addNodeRecursive в detached root
|
|
185
|
-
inst.el.appendChild(el)
|
|
186
|
-
inst.items.set(key, { rootPtr, el, jsSubtree: subtree })
|
|
187
|
-
withKey(key, p => module._vlistItemMounted(jsNode._id, p)) // измерение + кэш + якорение
|
|
188
|
-
},
|
|
189
|
-
command: (jsNode: any, name: string, ...args: any[]) => {
|
|
190
|
-
const inst = instances.get(jsNode._id)
|
|
191
|
-
if (!inst) return
|
|
192
|
-
const behavior = (animated: boolean) => animated ? "smooth" as const : "auto" as const
|
|
193
|
-
switch (name) {
|
|
194
|
-
case "scrollTo":
|
|
195
|
-
inst.el.scrollTo({ top: args[0], behavior: behavior(args[1] ?? true) })
|
|
196
|
-
break
|
|
197
|
-
case "scrollToKey": {
|
|
198
|
-
const off = withKey(args[0], p => module._vlistOffsetOf(inst.nodeId, p))
|
|
199
|
-
if (!isNaN(off)) inst.el.scrollTo({ top: off, behavior: behavior(args[1] ?? true) })
|
|
200
|
-
break
|
|
201
|
-
}
|
|
202
|
-
case "scrollToEnd": {
|
|
203
|
-
const h = module._getContentHeight(inst.nodeId)
|
|
204
|
-
inst.el.scrollTo({ top: h - inst.el.clientHeight, behavior: behavior(args[0] ?? true) })
|
|
205
|
-
break
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
},
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
return { register, updateLayout, unregister, refresh, api, instances }
|
|
1
|
+
import type { UIScreen } from './UIScreen'
|
|
2
|
+
|
|
3
|
+
type MountedItem = {
|
|
4
|
+
rootPtr: number // detached yoga root айтема
|
|
5
|
+
el: HTMLElement // DOM-корень поддерева
|
|
6
|
+
jsSubtree: any // JS-объект поддерева (для null _id при unmount)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type VListInstance = {
|
|
10
|
+
nodeId: number
|
|
11
|
+
el: HTMLElement
|
|
12
|
+
jsNode: any // VListElement
|
|
13
|
+
screen: UIScreen
|
|
14
|
+
pendingScroll: number | null
|
|
15
|
+
items: Map<string, MountedItem>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const createVListSystem = (module: any) => {
|
|
19
|
+
|
|
20
|
+
const instances = new Map<number, VListInstance>() // yogaId vlist-ноды → instance
|
|
21
|
+
|
|
22
|
+
// ---------- marshaling ----------
|
|
23
|
+
|
|
24
|
+
const withKey = <R>(key: string, fn: (ptr: number) => R): R => {
|
|
25
|
+
const len = module.lengthBytesUTF8(key) + 1
|
|
26
|
+
const p = module._malloc(len)
|
|
27
|
+
module.stringToUTF8(key, p, len)
|
|
28
|
+
try { return fn(p) } finally { module._free(p) }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const withStringArray = <R>(arr: string[], fn: (arrPtr: number) => R): R => {
|
|
32
|
+
const ptrs = arr.map(s => {
|
|
33
|
+
const len = module.lengthBytesUTF8(s) + 1
|
|
34
|
+
const p = module._malloc(len)
|
|
35
|
+
module.stringToUTF8(s, p, len)
|
|
36
|
+
return p
|
|
37
|
+
})
|
|
38
|
+
const arrPtr = module._malloc(Math.max(4 * ptrs.length, 4))
|
|
39
|
+
ptrs.forEach((p, i) => { module.HEAPU32[(arrPtr >> 2) + i] = p })
|
|
40
|
+
try { return fn(arrPtr) } finally {
|
|
41
|
+
ptrs.forEach(p => module._free(p))
|
|
42
|
+
module._free(arrPtr)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const withFloatArray = <R>(arr: number[], fn: (ptr: number) => R): R => {
|
|
47
|
+
const p = module._malloc(Math.max(4 * arr.length, 4))
|
|
48
|
+
arr.forEach((v, i) => { module.HEAPF32[(p >> 2) + i] = v })
|
|
49
|
+
try { return fn(p) } finally { module._free(p) }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const readStringArray = (arrPtr: number, count: number): string[] => {
|
|
53
|
+
const out: string[] = []
|
|
54
|
+
for (let i = 0; i < count; i++) {
|
|
55
|
+
out.push(module.UTF8ToString(module.HEAPU32[(arrPtr >> 2) + i]))
|
|
56
|
+
}
|
|
57
|
+
return out
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ---------- layout-проход по айтемам ----------
|
|
61
|
+
|
|
62
|
+
const refresh = (inst: VListInstance) => {
|
|
63
|
+
for (const [key, item] of inst.items) {
|
|
64
|
+
inst.screen.updateSizesFor(item.el)
|
|
65
|
+
item.el.style.top = withKey(key, p => module._vlistOffsetOf(inst.nodeId, p)) + "px"
|
|
66
|
+
item.el.style.left = "0px"
|
|
67
|
+
}
|
|
68
|
+
const h = module._getContentHeight(inst.nodeId)
|
|
69
|
+
inst.el.style.setProperty("--scroll-height", h + "px")
|
|
70
|
+
inst.el.style.setProperty("--scroll-width", "0px")
|
|
71
|
+
|
|
72
|
+
if (inst.pendingScroll !== null) {
|
|
73
|
+
if (Math.abs(inst.el.scrollTop - inst.pendingScroll) >= 1) {
|
|
74
|
+
inst.el.scrollTop = inst.pendingScroll // высота уже выставлена — кламп не сработает
|
|
75
|
+
}
|
|
76
|
+
inst.pendingScroll = null
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ---------- колбэки из C++ (регистрируются один раз) ----------
|
|
81
|
+
|
|
82
|
+
// g_vlSync
|
|
83
|
+
const syncFunc = module.addFunction(
|
|
84
|
+
(_screenId: number, nodePtr: number, unmountArr: number, unmountCount: number, mountArr: number, mountCount: number) => {
|
|
85
|
+
const inst = instances.get(nodePtr)
|
|
86
|
+
if (!inst) return
|
|
87
|
+
const unmountKeys = readStringArray(unmountArr, unmountCount)
|
|
88
|
+
const mountKeys = readStringArray(mountArr, mountCount)
|
|
89
|
+
|
|
90
|
+
// Контракт: интеграционный слой сносит поддеревья ДО вызова JS _syncWindow
|
|
91
|
+
for (const key of unmountKeys) {
|
|
92
|
+
const item = inst.items.get(key)
|
|
93
|
+
if (!item) continue
|
|
94
|
+
inst.items.delete(key)
|
|
95
|
+
item.el.remove()
|
|
96
|
+
inst.screen.releaseSubtree(item.jsSubtree) // nodeMap-чистка + _id = 0
|
|
97
|
+
withKey(key, p => module._vlistItemUnmounted(nodePtr, p)) // free yoga root
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
inst.jsNode._syncWindow(unmountKeys, mountKeys) // mount: render() → vlistMount()
|
|
101
|
+
refresh(inst)
|
|
102
|
+
}, "viiiiii")
|
|
103
|
+
|
|
104
|
+
// g_vlAdjust — якорение / кламп
|
|
105
|
+
const adjustFunc = module.addFunction((_screenId: number, nodePtr: number, scrollY: number) => {
|
|
106
|
+
const inst = instances.get(nodePtr)
|
|
107
|
+
if (!inst) return
|
|
108
|
+
inst.pendingScroll = scrollY // применим в refresh, после высоты
|
|
109
|
+
}, "viif")
|
|
110
|
+
|
|
111
|
+
// g_vlEdge
|
|
112
|
+
const edgeFunc = module.addFunction((_screenId: number, nodePtr: number, endEdge: number) => {
|
|
113
|
+
const inst = instances.get(nodePtr)
|
|
114
|
+
if (!inst) return
|
|
115
|
+
const listeners = endEdge ? (inst.jsNode.erl ?? []) : (inst.jsNode.strl ?? [])
|
|
116
|
+
for (const cb of listeners) cb()
|
|
117
|
+
}, "viii")
|
|
118
|
+
|
|
119
|
+
module._setVListHandlers(syncFunc, adjustFunc, edgeFunc)
|
|
120
|
+
|
|
121
|
+
// ---------- создание (из UIScreen.addNodeRecursive для type === "vlist") ----------
|
|
122
|
+
|
|
123
|
+
const register = (screen: UIScreen, jsNode: any, el: HTMLElement, yogaId: number) => {
|
|
124
|
+
const inst: VListInstance = { nodeId: yogaId, el, jsNode, screen, items: new Map(), pendingScroll: null }
|
|
125
|
+
instances.set(yogaId, inst)
|
|
126
|
+
|
|
127
|
+
module._vlistInit(yogaId, jsNode._overscan, jsNode._inverted ? 1 : 0,
|
|
128
|
+
jsNode._startThreshold, jsNode._endThreshold)
|
|
129
|
+
|
|
130
|
+
if (jsNode._keys.length > 0) {
|
|
131
|
+
setKeysInternal(yogaId, jsNode._keys, jsNode._estimates)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
el.addEventListener("scroll", () => {
|
|
135
|
+
module._vlistOnScroll(yogaId, el.scrollTop)
|
|
136
|
+
refresh(inst)
|
|
137
|
+
for (const cb of jsNode.sl ?? []) cb(el.scrollTop)
|
|
138
|
+
})
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Вызывается из UIScreen.updateSizes для vlist-элемента (после выставления его размеров)
|
|
142
|
+
const updateLayout = (yogaId: number) => {
|
|
143
|
+
const inst = instances.get(yogaId)
|
|
144
|
+
if (!inst) return
|
|
145
|
+
module._vlistUpdateLayout(yogaId) // внутри может дернуть sync → mount
|
|
146
|
+
refresh(inst)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const unregister = (yogaId: number) => {
|
|
150
|
+
const inst = instances.get(yogaId)
|
|
151
|
+
if (!inst) return
|
|
152
|
+
module._vlistFree(yogaId) // освобождает detached-корни
|
|
153
|
+
instances.delete(yogaId)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const setKeysInternal = (yogaId: number, keys: string[], estimates: number[]) => {
|
|
157
|
+
withStringArray(keys, kp =>
|
|
158
|
+
withFloatArray(estimates, ep =>
|
|
159
|
+
module._vlistSetKeys(yogaId, kp, ep, keys.length)))
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ---------- API для _creatorUI (зовётся из VListElement) ----------
|
|
163
|
+
|
|
164
|
+
const api = {
|
|
165
|
+
vlistSetKeys: (jsNode: any, keys: string[], estimates: number[]) => {
|
|
166
|
+
setKeysInternal(jsNode._id, keys, estimates)
|
|
167
|
+
},
|
|
168
|
+
vlistInsertKeys: (jsNode: any, index: number, keys: string[], estimates: number[]) => {
|
|
169
|
+
withStringArray(keys, kp =>
|
|
170
|
+
withFloatArray(estimates, ep =>
|
|
171
|
+
module._vlistInsertKeys(jsNode._id, index, kp, ep, keys.length)))
|
|
172
|
+
},
|
|
173
|
+
vlistRemoveKeys: (jsNode: any, keys: string[]) => {
|
|
174
|
+
withStringArray(keys, kp => module._vlistRemoveKeys(jsNode._id, kp, keys.length))
|
|
175
|
+
},
|
|
176
|
+
vlistInvalidate: (jsNode: any, key: string) => {
|
|
177
|
+
withKey(key, p => module._vlistInvalidate(jsNode._id, p))
|
|
178
|
+
},
|
|
179
|
+
// Вызывается из _syncWindow на каждый mount-ключ
|
|
180
|
+
vlistMount: (jsNode: any, key: string, subtree: any) => {
|
|
181
|
+
const inst = instances.get(jsNode._id)
|
|
182
|
+
if (!inst) return
|
|
183
|
+
const rootPtr = withKey(key, p => module._vlistCreateItemRoot(jsNode._id, p))
|
|
184
|
+
const el = inst.screen.buildSubtree(subtree, rootPtr) // addNodeRecursive в detached root
|
|
185
|
+
inst.el.appendChild(el)
|
|
186
|
+
inst.items.set(key, { rootPtr, el, jsSubtree: subtree })
|
|
187
|
+
withKey(key, p => module._vlistItemMounted(jsNode._id, p)) // измерение + кэш + якорение
|
|
188
|
+
},
|
|
189
|
+
command: (jsNode: any, name: string, ...args: any[]) => {
|
|
190
|
+
const inst = instances.get(jsNode._id)
|
|
191
|
+
if (!inst) return
|
|
192
|
+
const behavior = (animated: boolean) => animated ? "smooth" as const : "auto" as const
|
|
193
|
+
switch (name) {
|
|
194
|
+
case "scrollTo":
|
|
195
|
+
inst.el.scrollTo({ top: args[0], behavior: behavior(args[1] ?? true) })
|
|
196
|
+
break
|
|
197
|
+
case "scrollToKey": {
|
|
198
|
+
const off = withKey(args[0], p => module._vlistOffsetOf(inst.nodeId, p))
|
|
199
|
+
if (!isNaN(off)) inst.el.scrollTo({ top: off, behavior: behavior(args[1] ?? true) })
|
|
200
|
+
break
|
|
201
|
+
}
|
|
202
|
+
case "scrollToEnd": {
|
|
203
|
+
const h = module._getContentHeight(inst.nodeId)
|
|
204
|
+
inst.el.scrollTo({ top: h - inst.el.clientHeight, behavior: behavior(args[0] ?? true) })
|
|
205
|
+
break
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return { register, updateLayout, unregister, refresh, api, instances }
|
|
212
212
|
}
|
package/src/utils/imageNode.ts
CHANGED
|
@@ -99,6 +99,11 @@ function configureImageSrc(host: ImageHost, el: HTMLElement, src: ImageSrc, tint
|
|
|
99
99
|
;(el as any)._canvasSurface = surfaceId
|
|
100
100
|
el.removeAttribute("data-viewbox")
|
|
101
101
|
el.src = host.canvasHost?.surfaceToDataUrl(surfaceId) ?? ""
|
|
102
|
+
} else if (typeof src === "object" && "scene2d" in src) {
|
|
103
|
+
// UIImage(scene2d): a live 2D scene drawn into the box — desktop only so far (parity
|
|
104
|
+
// ui-scene2d-image); a blank box here rather than a bogus blob url.
|
|
105
|
+
el.removeAttribute("data-viewbox")
|
|
106
|
+
el.removeAttribute("src")
|
|
102
107
|
} else {
|
|
103
108
|
el.removeAttribute("data-viewbox")
|
|
104
109
|
el.src = typeof src === "object" ? getUrl(src._id) : src
|
|
@@ -440,6 +440,17 @@ export const createViewer = async (canvas: HTMLCanvasElement, options: ViewerOpt
|
|
|
440
440
|
// Screen inputs are logical px; scale to backing px for the native (viewport-space) pick.
|
|
441
441
|
raycastView: (offsetX: number, offsetY: number) => module._raycastView(offsetX * resFactor, offsetY * resFactor),
|
|
442
442
|
setCulling: module._setCulling,
|
|
443
|
+
// Draw order / per-instance depth state (Mesh.renderPriority, Material.depthTest / depthWrite) —
|
|
444
|
+
// only once the wasm carries the exports; the SDK feature-detects them.
|
|
445
|
+
setRenderPriority: module._setRenderPriority,
|
|
446
|
+
setMaterialDepthTest: module._setMaterialDepthTest ? (id: number, on: boolean) => module._setMaterialDepthTest(id, on ? 1 : 0) : undefined,
|
|
447
|
+
setMaterialDepthWrite: module._setMaterialDepthWrite ? (id: number, on: boolean) => module._setMaterialDepthWrite(id, on ? 1 : 0) : undefined,
|
|
448
|
+
setMaterialCulling: module._setMaterialCulling,
|
|
449
|
+
setMaterialStencil: module._setMaterialStencil
|
|
450
|
+
? (id: number, write: boolean, test: number, ref: number, onPass: number, onFail: number, onDepthFail: number, readMask: number, writeMask: number) =>
|
|
451
|
+
module._setMaterialStencil(id, write ? 1 : 0, test, ref, onPass, onFail, onDepthFail, readMask, writeMask)
|
|
452
|
+
: undefined,
|
|
453
|
+
setSceneStencil: module._setSceneStencil ? (sceneId: number, on: boolean) => module._setSceneStencil(sceneId, on ? 1 : 0) : undefined,
|
|
443
454
|
setGlbCulling: module._setGlbCulling,
|
|
444
455
|
// Culling default for animated GLBs (Model.load): only once the wasm carries the export — an older
|
|
445
456
|
// binary leaves the SDK on the always-draw default (see bridges.d.ts).
|
|
@@ -615,7 +626,7 @@ export const createViewer = async (canvas: HTMLCanvasElement, options: ViewerOpt
|
|
|
615
626
|
createMaterial: (systemId: number) => {
|
|
616
627
|
return module._createMaterialInstance(compileMaterial(systemId))
|
|
617
628
|
},
|
|
618
|
-
setMesh(entityId: number, materialId: number, vertices: Float32Array, normals: Float32Array, indices: Uint16Array, uv: Float32Array, meshType: number, uv1?: Float32Array) {
|
|
629
|
+
setMesh(entityId: number, materialId: number, vertices: Float32Array, normals: Float32Array, indices: Uint16Array, uv: Float32Array, meshType: number, uv1?: Float32Array, colors?: Uint8Array) {
|
|
619
630
|
const ptrVertices = module._malloc(vertices.byteLength)
|
|
620
631
|
const ptrNormals = module._malloc(normals.byteLength)
|
|
621
632
|
const ptrIndices = module._malloc(indices.byteLength)
|
|
@@ -625,6 +636,15 @@ export const createViewer = async (canvas: HTMLCanvasElement, options: ViewerOpt
|
|
|
625
636
|
module.HEAPU16.set(indices, ptrIndices/2)
|
|
626
637
|
module.HEAPF32.set(uv, ptrUv/4)
|
|
627
638
|
|
|
639
|
+
// the vertex colours ride the newest export (createMeshC); a wasm without it draws white
|
|
640
|
+
if (colors && module._createMeshC) {
|
|
641
|
+
const ptrUv1 = uv1 ? module._malloc(uv1.byteLength) : 0
|
|
642
|
+
if (uv1 && ptrUv1) module.HEAPF32.set(uv1, ptrUv1 / 4)
|
|
643
|
+
const ptrColors = module._malloc(colors.byteLength)
|
|
644
|
+
module.HEAPU8.set(colors, ptrColors)
|
|
645
|
+
module._createMeshC(entityId, materialId, ptrVertices, ptrNormals, ptrIndices, ptrUv, vertices.length / 3, indices.length, meshType, ptrUv1, ptrColors)
|
|
646
|
+
return
|
|
647
|
+
}
|
|
628
648
|
// the lightmap UV set rides the newer export; a wasm without it duplicates uv into UV1
|
|
629
649
|
if (uv1 && module._createMeshEx) {
|
|
630
650
|
const ptrUv1 = module._malloc(uv1.byteLength)
|
|
@@ -832,7 +852,11 @@ export const createViewer = async (canvas: HTMLCanvasElement, options: ViewerOpt
|
|
|
832
852
|
return out
|
|
833
853
|
},
|
|
834
854
|
getGlbClipSet: (entityId: number) => module._getGlbClipSet(entityId),
|
|
835
|
-
|
|
855
|
+
deriveClip: (clipSetId: number, clip: number, mirror: boolean, start: number, end: number) => {
|
|
856
|
+
if (typeof module._deriveClip === "function") return module._deriveClip(clipSetId, clip, mirror ? 1 : 0, start, end)
|
|
857
|
+
if (mirror) { console.warn("creator-gl: this wasm cannot mirror clips — rebuild it"); return 0 } // a wasm from before 2026-09-06
|
|
858
|
+
return module._sliceClip(clipSetId, clip, start < 0 ? 0 : start, end < 0 ? 1e9 : end)
|
|
859
|
+
},
|
|
836
860
|
destroyClipSet: (clipSetId: number) => module._destroyClipSet(clipSetId),
|
|
837
861
|
animatorCreate: (entityId: number) => module._animatorCreate(entityId),
|
|
838
862
|
animatorDestroy: (animatorId: number) => module._animatorDestroy(animatorId),
|
|
File without changes
|
|
Binary file
|
|
Binary file
|
|
File without changes
|