lecodes-viewer 0.20.0 → 1.0.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/dist-embed/VERSION.json +3 -3
- package/dist-embed/assets/createViewerLite-Cl5IRhWb.js +866 -0
- package/dist-embed/assets/{host-Czj6Nu-U.js → host-9ufZhYXu.js} +2 -2
- package/dist-embed/assets/{index-mhLEVWyK.js → index-BO_sQ75w.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 +33 -7
- 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/dist-embed/assets/createViewerLite-DkM-O9BQ.js +0 -852
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lecodes-viewer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"main": "src/createViewer.ts",
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"lecodes-renderer": "0.
|
|
6
|
+
"lecodes-renderer": "1.0.0",
|
|
7
7
|
"ogl": "^1.0.11",
|
|
8
|
-
"lecodes-viewer-lite": "0.
|
|
8
|
+
"lecodes-viewer-lite": "1.0.0"
|
|
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
|
|
@@ -233,7 +233,7 @@ export const createViewer = async (canvas: HTMLCanvasElement, options: ViewerOpt
|
|
|
233
233
|
// Each read-back writes into a caller-owned `out`, so there's no shared-buffer aliasing — we only
|
|
234
234
|
// drop the per-call malloc/free + view allocation. Read module.HEAPF32 FRESH each call (emscripten
|
|
235
235
|
// swaps that view on heap growth; the scratch address survives).
|
|
236
|
-
const READ_SCRATCH =
|
|
236
|
+
const READ_SCRATCH = 128 // largest reader: vehicleGetState = 11 + 11 per wheel
|
|
237
237
|
const readScratchPtr = module._malloc(READ_SCRATCH * 4)
|
|
238
238
|
const readInto = (out: Float32Array, n: number, fn: (ptr: number) => void): void => {
|
|
239
239
|
fn(readScratchPtr)
|
|
@@ -396,16 +396,18 @@ export const createViewer = async (canvas: HTMLCanvasElement, options: ViewerOpt
|
|
|
396
396
|
return id
|
|
397
397
|
},
|
|
398
398
|
vehicleDestroy: (vehicleId: number) => module._vehicleDestroy(vehicleId),
|
|
399
|
-
vehicleSetTransmission: (vehicleId: number, ratio: number, clutch: number) =>
|
|
400
|
-
module._vehicleSetTransmission(vehicleId, ratio, clutch),
|
|
401
399
|
vehicleSetTuning: (vehicleId: number, settings: Float32Array) => {
|
|
402
400
|
const ptr = module._malloc(settings.byteLength)
|
|
403
401
|
module.HEAPF32.set(settings, ptr / 4)
|
|
404
402
|
module._vehicleSetTuning(vehicleId, ptr, settings.length)
|
|
405
403
|
module._free(ptr)
|
|
406
404
|
},
|
|
407
|
-
vehicleSetInput: (vehicleId: number,
|
|
408
|
-
module.
|
|
405
|
+
vehicleSetInput: (vehicleId: number, input: Float32Array) => {
|
|
406
|
+
const ptr = module._malloc(input.length * 4)
|
|
407
|
+
module.HEAPF32.set(input, ptr >> 2)
|
|
408
|
+
module._vehicleSetInput(vehicleId, ptr, input.length)
|
|
409
|
+
module._free(ptr)
|
|
410
|
+
},
|
|
409
411
|
vehicleGetState: (vehicleId: number, out: Float32Array) =>
|
|
410
412
|
readInto(out, Math.min(out.length, READ_SCRATCH), (ptr) => module._vehicleGetState(vehicleId, ptr, Math.min(out.length, READ_SCRATCH))),
|
|
411
413
|
vehicleReset: (vehicleId: number, x: number, y: number, z: number, qx: number, qy: number, qz: number, qw: number) =>
|
|
@@ -440,6 +442,17 @@ export const createViewer = async (canvas: HTMLCanvasElement, options: ViewerOpt
|
|
|
440
442
|
// Screen inputs are logical px; scale to backing px for the native (viewport-space) pick.
|
|
441
443
|
raycastView: (offsetX: number, offsetY: number) => module._raycastView(offsetX * resFactor, offsetY * resFactor),
|
|
442
444
|
setCulling: module._setCulling,
|
|
445
|
+
// Draw order / per-instance depth state (Mesh.renderPriority, Material.depthTest / depthWrite) —
|
|
446
|
+
// only once the wasm carries the exports; the SDK feature-detects them.
|
|
447
|
+
setRenderPriority: module._setRenderPriority,
|
|
448
|
+
setMaterialDepthTest: module._setMaterialDepthTest ? (id: number, on: boolean) => module._setMaterialDepthTest(id, on ? 1 : 0) : undefined,
|
|
449
|
+
setMaterialDepthWrite: module._setMaterialDepthWrite ? (id: number, on: boolean) => module._setMaterialDepthWrite(id, on ? 1 : 0) : undefined,
|
|
450
|
+
setMaterialCulling: module._setMaterialCulling,
|
|
451
|
+
setMaterialStencil: module._setMaterialStencil
|
|
452
|
+
? (id: number, write: boolean, test: number, ref: number, onPass: number, onFail: number, onDepthFail: number, readMask: number, writeMask: number) =>
|
|
453
|
+
module._setMaterialStencil(id, write ? 1 : 0, test, ref, onPass, onFail, onDepthFail, readMask, writeMask)
|
|
454
|
+
: undefined,
|
|
455
|
+
setSceneStencil: module._setSceneStencil ? (sceneId: number, on: boolean) => module._setSceneStencil(sceneId, on ? 1 : 0) : undefined,
|
|
443
456
|
setGlbCulling: module._setGlbCulling,
|
|
444
457
|
// Culling default for animated GLBs (Model.load): only once the wasm carries the export — an older
|
|
445
458
|
// binary leaves the SDK on the always-draw default (see bridges.d.ts).
|
|
@@ -615,7 +628,7 @@ export const createViewer = async (canvas: HTMLCanvasElement, options: ViewerOpt
|
|
|
615
628
|
createMaterial: (systemId: number) => {
|
|
616
629
|
return module._createMaterialInstance(compileMaterial(systemId))
|
|
617
630
|
},
|
|
618
|
-
setMesh(entityId: number, materialId: number, vertices: Float32Array, normals: Float32Array, indices: Uint16Array, uv: Float32Array, meshType: number, uv1?: Float32Array) {
|
|
631
|
+
setMesh(entityId: number, materialId: number, vertices: Float32Array, normals: Float32Array, indices: Uint16Array, uv: Float32Array, meshType: number, uv1?: Float32Array, colors?: Uint8Array) {
|
|
619
632
|
const ptrVertices = module._malloc(vertices.byteLength)
|
|
620
633
|
const ptrNormals = module._malloc(normals.byteLength)
|
|
621
634
|
const ptrIndices = module._malloc(indices.byteLength)
|
|
@@ -625,6 +638,15 @@ export const createViewer = async (canvas: HTMLCanvasElement, options: ViewerOpt
|
|
|
625
638
|
module.HEAPU16.set(indices, ptrIndices/2)
|
|
626
639
|
module.HEAPF32.set(uv, ptrUv/4)
|
|
627
640
|
|
|
641
|
+
// the vertex colours ride the newest export (createMeshC); a wasm without it draws white
|
|
642
|
+
if (colors && module._createMeshC) {
|
|
643
|
+
const ptrUv1 = uv1 ? module._malloc(uv1.byteLength) : 0
|
|
644
|
+
if (uv1 && ptrUv1) module.HEAPF32.set(uv1, ptrUv1 / 4)
|
|
645
|
+
const ptrColors = module._malloc(colors.byteLength)
|
|
646
|
+
module.HEAPU8.set(colors, ptrColors)
|
|
647
|
+
module._createMeshC(entityId, materialId, ptrVertices, ptrNormals, ptrIndices, ptrUv, vertices.length / 3, indices.length, meshType, ptrUv1, ptrColors)
|
|
648
|
+
return
|
|
649
|
+
}
|
|
628
650
|
// the lightmap UV set rides the newer export; a wasm without it duplicates uv into UV1
|
|
629
651
|
if (uv1 && module._createMeshEx) {
|
|
630
652
|
const ptrUv1 = module._malloc(uv1.byteLength)
|
|
@@ -832,7 +854,11 @@ export const createViewer = async (canvas: HTMLCanvasElement, options: ViewerOpt
|
|
|
832
854
|
return out
|
|
833
855
|
},
|
|
834
856
|
getGlbClipSet: (entityId: number) => module._getGlbClipSet(entityId),
|
|
835
|
-
|
|
857
|
+
deriveClip: (clipSetId: number, clip: number, mirror: boolean, start: number, end: number) => {
|
|
858
|
+
if (typeof module._deriveClip === "function") return module._deriveClip(clipSetId, clip, mirror ? 1 : 0, start, end)
|
|
859
|
+
if (mirror) { console.warn("creator-gl: this wasm cannot mirror clips — rebuild it"); return 0 } // a wasm from before 2026-09-06
|
|
860
|
+
return module._sliceClip(clipSetId, clip, start < 0 ? 0 : start, end < 0 ? 1e9 : end)
|
|
861
|
+
},
|
|
836
862
|
destroyClipSet: (clipSetId: number) => module._destroyClipSet(clipSetId),
|
|
837
863
|
animatorCreate: (entityId: number) => module._animatorCreate(entityId),
|
|
838
864
|
animatorDestroy: (animatorId: number) => module._animatorDestroy(animatorId),
|
|
File without changes
|
|
Binary file
|
|
Binary file
|
|
File without changes
|