babylon.quarks-editor 0.18.1 → 0.20.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.
Files changed (157) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +6 -6
  3. package/dist/core/assetDragSession.d.ts +4 -0
  4. package/dist/core/assetDragSession.js +8 -0
  5. package/dist/core/assetTree.d.ts +39 -0
  6. package/dist/core/assetTree.js +112 -0
  7. package/dist/core/assets.d.ts +56 -0
  8. package/dist/core/assets.js +207 -0
  9. package/dist/core/binding.d.ts +6 -1
  10. package/dist/core/binding.js +7 -0
  11. package/dist/core/collision.d.ts +4 -5
  12. package/dist/core/collision.js +4 -5
  13. package/dist/core/colors.d.ts +1 -1
  14. package/dist/core/defaultEffect.d.ts +4 -5
  15. package/dist/core/defaultEffect.js +5 -14
  16. package/dist/core/effectTree.d.ts +1 -1
  17. package/dist/core/effectTree.js +18 -4
  18. package/dist/core/emitterShapeWireframe.d.ts +2 -1
  19. package/dist/core/emitterShapeWireframe.js +2 -1
  20. package/dist/core/galleryDropMarker.js +1 -1
  21. package/dist/core/galleryPlayback.d.ts +7 -7
  22. package/dist/core/galleryPlayback.js +2 -2
  23. package/dist/core/galleryScene.d.ts +8 -13
  24. package/dist/core/galleryScene.js +14 -42
  25. package/dist/core/gallerySelectionMarker.d.ts +1 -0
  26. package/dist/core/gallerySelectionMarker.js +1 -0
  27. package/dist/core/history.d.ts +12 -1
  28. package/dist/core/history.js +16 -3
  29. package/dist/core/indexedDbProjectStore.d.ts +35 -0
  30. package/dist/core/indexedDbProjectStore.js +299 -0
  31. package/dist/core/lineShaders.d.ts +2 -0
  32. package/dist/core/lineShaders.js +7 -0
  33. package/dist/core/loadEffect.d.ts +2 -2
  34. package/dist/core/loadEffect.js +1 -1
  35. package/dist/core/pickAssetFiles.d.ts +6 -0
  36. package/dist/core/{pickGalleryFiles.js → pickAssetFiles.js} +13 -8
  37. package/dist/core/projectStore.d.ts +61 -0
  38. package/dist/core/projectStore.js +14 -0
  39. package/dist/core/sceneEntries.d.ts +35 -0
  40. package/dist/core/sceneEntries.js +62 -0
  41. package/dist/core/shapes.d.ts +1 -1
  42. package/dist/core/shapes.js +35 -8
  43. package/dist/core/systems.d.ts +1 -1
  44. package/dist/core/systems.js +3 -1
  45. package/dist/core/timeline.d.ts +1 -2
  46. package/dist/core/transport.d.ts +12 -0
  47. package/dist/core/transport.js +36 -0
  48. package/dist/core/values.d.ts +1 -1
  49. package/dist/core/viewportPicker.d.ts +4 -4
  50. package/dist/core/viewportPicker.js +3 -3
  51. package/dist/index.d.ts +15 -11
  52. package/dist/index.js +8 -6
  53. package/dist/react/AssetsPanel.d.ts +22 -0
  54. package/dist/react/AssetsPanel.js +252 -0
  55. package/dist/react/ChoiceDialog.d.ts +18 -0
  56. package/dist/react/ChoiceDialog.js +48 -0
  57. package/dist/react/ContextMenu.d.ts +16 -0
  58. package/dist/react/ContextMenu.js +70 -0
  59. package/dist/react/CurveEditor.d.ts +1 -2
  60. package/dist/react/CurveEditor.js +14 -3
  61. package/dist/react/EditorWorkspace.d.ts +21 -0
  62. package/dist/react/EditorWorkspace.js +63 -0
  63. package/dist/react/EffectEditor.d.ts +3 -3
  64. package/dist/react/EffectEditor.js +9 -6
  65. package/dist/react/EffectEditorHost.d.ts +20 -3
  66. package/dist/react/EffectEditorHost.js +1426 -558
  67. package/dist/react/EffectTreeView.d.ts +17 -0
  68. package/dist/react/EffectTreeView.js +52 -0
  69. package/dist/react/GradientEditor.d.ts +1 -2
  70. package/dist/react/GradientEditor.js +8 -1
  71. package/dist/react/MessageDialog.js +7 -1
  72. package/dist/react/ModuleSection.js +10 -3
  73. package/dist/react/ObjectModule.d.ts +5 -2
  74. package/dist/react/ObjectModule.js +8 -11
  75. package/dist/react/ProjectsPage.d.ts +9 -0
  76. package/dist/react/ProjectsPage.js +140 -0
  77. package/dist/react/ScenePanel.d.ts +26 -0
  78. package/dist/react/ScenePanel.js +134 -0
  79. package/dist/react/SubEmittersModule.d.ts +1 -2
  80. package/dist/react/TimelinePanel.d.ts +11 -4
  81. package/dist/react/TimelinePanel.js +65 -40
  82. package/dist/react/TimelineTrackRow.d.ts +1 -1
  83. package/dist/react/TimelineTrackRow.js +20 -6
  84. package/dist/react/TransportBar.d.ts +24 -0
  85. package/dist/react/TransportBar.js +43 -0
  86. package/dist/react/ValueField.d.ts +1 -2
  87. package/dist/react/behaviorModules.js +24 -12
  88. package/dist/react/index.d.ts +31 -17
  89. package/dist/react/index.js +18 -11
  90. package/dist/react/inspectorHints.js +1 -2
  91. package/dist/react/modules.js +55 -25
  92. package/package.json +68 -68
  93. package/src/core/assetDragSession.ts +12 -0
  94. package/src/core/assetTree.ts +148 -0
  95. package/src/core/assets.ts +262 -0
  96. package/src/core/binding.ts +10 -2
  97. package/src/core/collision.ts +4 -5
  98. package/src/core/colors.ts +1 -1
  99. package/src/core/defaultEffect.ts +10 -23
  100. package/src/core/effectTree.ts +22 -7
  101. package/src/core/emitterShapeWireframe.ts +9 -4
  102. package/src/core/galleryDropMarker.ts +6 -2
  103. package/src/core/galleryPlayback.ts +9 -9
  104. package/src/core/galleryScene.ts +15 -63
  105. package/src/core/gallerySelectionMarker.ts +6 -1
  106. package/src/core/history.ts +24 -3
  107. package/src/core/indexedDbProjectStore.ts +357 -0
  108. package/src/core/lineShaders.ts +7 -0
  109. package/src/core/loadEffect.ts +3 -3
  110. package/src/core/{pickGalleryFiles.ts → pickAssetFiles.ts} +16 -8
  111. package/src/core/projectStore.ts +81 -0
  112. package/src/core/sceneEntries.ts +96 -0
  113. package/src/core/shapes.ts +48 -10
  114. package/src/core/systems.ts +9 -3
  115. package/src/core/timeline.ts +8 -8
  116. package/src/core/transport.ts +53 -0
  117. package/src/core/values.ts +1 -1
  118. package/src/core/viewportPicker.ts +5 -5
  119. package/src/index.ts +29 -18
  120. package/src/react/AssetsPanel.tsx +559 -0
  121. package/src/react/ChoiceDialog.tsx +93 -0
  122. package/src/react/ContextMenu.tsx +107 -0
  123. package/src/react/CurveEditor.tsx +40 -10
  124. package/src/react/EditorWorkspace.tsx +95 -0
  125. package/src/react/EffectEditor.tsx +28 -16
  126. package/src/react/EffectEditorHost.tsx +2005 -721
  127. package/src/react/EffectTreeView.tsx +117 -0
  128. package/src/react/GradientEditor.tsx +21 -4
  129. package/src/react/InspectorInfoIcon.tsx +6 -2
  130. package/src/react/MessageDialog.tsx +9 -1
  131. package/src/react/ModuleSection.tsx +12 -3
  132. package/src/react/ObjectModule.tsx +41 -16
  133. package/src/react/ProjectsPage.tsx +306 -0
  134. package/src/react/PromptDialog.tsx +5 -1
  135. package/src/react/ScenePanel.tsx +318 -0
  136. package/src/react/SubEmittersModule.tsx +1 -2
  137. package/src/react/TimelinePanel.tsx +171 -99
  138. package/src/react/TimelineTrackRow.tsx +71 -18
  139. package/src/react/TransportBar.tsx +114 -0
  140. package/src/react/ValueField.tsx +0 -1
  141. package/src/react/behaviorModules.tsx +148 -42
  142. package/src/react/index.ts +47 -33
  143. package/src/react/inspectorHints.ts +1 -2
  144. package/src/react/modules.tsx +173 -57
  145. package/dist/core/galleryDragSession.d.ts +0 -4
  146. package/dist/core/galleryDragSession.js +0 -8
  147. package/dist/core/galleryTree.d.ts +0 -49
  148. package/dist/core/galleryTree.js +0 -138
  149. package/dist/core/loadEffectGallery.d.ts +0 -36
  150. package/dist/core/loadEffectGallery.js +0 -97
  151. package/dist/core/pickGalleryFiles.d.ts +0 -5
  152. package/dist/react/GalleryPanel.d.ts +0 -20
  153. package/dist/react/GalleryPanel.js +0 -246
  154. package/src/core/galleryDragSession.ts +0 -12
  155. package/src/core/galleryTree.ts +0 -187
  156. package/src/core/loadEffectGallery.ts +0 -146
  157. package/src/react/GalleryPanel.tsx +0 -517
@@ -1,10 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { ArrowPathIcon, ForwardIcon, PauseIcon, PlayIcon, RectangleGroupIcon } from '@heroicons/react/24/solid';
2
3
  import React, { useMemo, useRef, useState } from 'react';
3
- import { ArrowPathIcon, ForwardIcon, PauseIcon, PlayIcon, RectangleGroupIcon, } from '@heroicons/react/24/solid';
4
- import { clearFocusFinished, getFocusElapsed, isFocusFinished, markFocusFinished, setFocusElapsed, } from '../core/playback';
5
4
  import { groupNodes } from '../core/groups';
5
+ import { clearFocusFinished, getFocusElapsed, isFocusFinished, markFocusFinished, setFocusElapsed, } from '../core/playback';
6
6
  import { pauseFocus, playFocus, scrubFocusTo, scrubFocusToSettled } from '../core/scrub';
7
7
  import { computeTimelineRows, computeTimelineSpan } from '../core/timeline';
8
+ import { PLAYBACK_SPEEDS, restartFocusPlayback, toggleStagePlayback } from '../core/transport';
8
9
  import { TimelineTrackRow } from './TimelineTrackRow';
9
10
  import { iconStyle } from './icons';
10
11
  import { buttonStyle, theme } from './theme';
@@ -29,7 +30,7 @@ function pickTickInterval(pxPerSecond) {
29
30
  * apply only to the focused effect without stopping other previews.
30
31
  */
31
32
  export function TimelinePanel(props) {
32
- const { binding, selectedNode, onSelectNode, scene, renderer, playbackRef, playheadRef, counterRef, stagePlaying, setStagePlaying, speed, setSpeed, playbackEnabled = true } = props;
33
+ const { binding, selectedNode, onSelectNode, scene, renderer, playbackRef, playheadRef, counterRef, stagePlaying, setStagePlaying, speed, setSpeed, playbackEnabled = true, docked = false, showTransport = true, } = props;
33
34
  const [bodyWidthPx, setBodyWidthPx] = useState(600);
34
35
  const [collapsedGroups, setCollapsedGroups] = useState(() => new Set());
35
36
  const [hiddenSystems, setHiddenSystems] = useState(() => new Set());
@@ -129,7 +130,18 @@ export function TimelinePanel(props) {
129
130
  };
130
131
  raf = requestAnimationFrame(tick);
131
132
  return () => cancelAnimationFrame(raf);
132
- }, [pxPerSecond, timelineSpan, anyLooping, playbackRef, playheadRef, focusRootId, playbackEnabled, binding, scene, renderer]);
133
+ }, [
134
+ pxPerSecond,
135
+ timelineSpan,
136
+ anyLooping,
137
+ playbackRef,
138
+ playheadRef,
139
+ focusRootId,
140
+ playbackEnabled,
141
+ binding,
142
+ scene,
143
+ renderer,
144
+ ]);
133
145
  const visibleRows = useMemo(() => {
134
146
  let hideUntilDepth = null;
135
147
  const visible = rows.filter((row) => {
@@ -196,34 +208,12 @@ export function TimelinePanel(props) {
196
208
  }
197
209
  };
198
210
  const restartFocus = () => {
199
- scrubFocusTo(binding, renderer, 0);
200
- const state = playbackRef.current;
201
- setFocusElapsed(state, focusRootId, 0);
202
- clearFocusFinished(state, focusRootId);
203
- if (stagePlaying) {
204
- playFocus(binding);
205
- }
211
+ restartFocusPlayback(binding, renderer, scene, playbackRef.current);
206
212
  if (playheadRef.current) {
207
213
  playheadRef.current.style.left = `${LABEL_WIDTH + TIMELINE_INSET}px`;
208
214
  }
209
- scene.render();
210
- };
211
- const handleStagePlayPause = () => {
212
- const state = playbackRef.current;
213
- if (!stagePlaying) {
214
- if (isFocusFinished(state, focusRootId)) {
215
- binding.restart();
216
- clearFocusFinished(state, focusRootId);
217
- setFocusElapsed(state, focusRootId, 0);
218
- scene.render();
219
- }
220
- state.stagePlaying = true;
221
- setStagePlaying(true);
222
- return;
223
- }
224
- state.stagePlaying = false;
225
- setStagePlaying(false);
226
215
  };
216
+ const handleStagePlayPause = () => toggleStagePlayback(binding, scene, playbackRef.current, stagePlaying, setStagePlaying);
227
217
  const ticks = [];
228
218
  for (let t = 0; t <= timelineSpan; t += pickTickInterval(pxPerSecond)) {
229
219
  ticks.push(t);
@@ -238,19 +228,31 @@ export function TimelinePanel(props) {
238
228
  gap: 6,
239
229
  };
240
230
  return (_jsxs("div", { style: {
241
- flexShrink: 0,
242
- height: PANEL_HEIGHT,
243
- borderTop: `1px solid ${theme.border}`,
231
+ ...(docked
232
+ ? { flex: 1, minHeight: 0 }
233
+ : { flexShrink: 0, height: PANEL_HEIGHT, borderTop: `1px solid ${theme.border}` }),
244
234
  background: theme.panelBg,
245
235
  display: 'flex',
246
236
  flexDirection: 'column',
247
237
  fontFamily: theme.font,
248
238
  color: theme.text,
249
- }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 6, padding: '6px 10px', borderBottom: `1px solid ${theme.border}` }, children: [_jsx("button", { className: "qe-hover", style: { ...overlayButton, opacity: playbackEnabled ? 1 : 0.45 }, title: stagePlaying ? 'Pause stage preview' : 'Play stage preview', disabled: !playbackEnabled, onClick: handleStagePlayPause, children: stagePlaying ? _jsx(PauseIcon, { style: iconStyle(14) }) : _jsx(PlayIcon, { style: iconStyle(14) }) }), _jsx("button", { className: "qe-hover", style: { ...overlayButton, opacity: playbackEnabled ? 1 : 0.45 }, title: "Step one frame (stage)", disabled: !playbackEnabled || stagePlaying, onClick: () => (playbackRef.current.stepQueued = true), children: _jsx(ForwardIcon, { style: iconStyle(14) }) }), _jsx("button", { className: "qe-hover", style: { ...overlayButton, opacity: playbackEnabled ? 1 : 0.45 }, title: "Restart focused effect only", disabled: !playbackEnabled, onClick: restartFocus, children: _jsx(ArrowPathIcon, { style: iconStyle(14) }) }), _jsx("select", { className: "qe-hover", style: { ...overlayButton, padding: '5px 4px', opacity: playbackEnabled ? 1 : 0.45 }, title: "Playback speed", value: speed, disabled: !playbackEnabled, onChange: (e) => {
250
- const next = Number(e.target.value);
251
- playbackRef.current.speed = next;
252
- setSpeed(next);
253
- }, children: [0.1, 0.25, 0.5, 1, 2, 4].map((s) => (_jsxs("option", { value: s, children: ["\u00D7", s] }, s))) }), _jsx("span", { ref: counterRef, style: { fontSize: 12, color: theme.textDim, fontVariantNumeric: 'tabular-nums', paddingLeft: 4, minWidth: 120 } }), groupSelection.size >= 2 && (_jsxs("button", { className: "qe-hover", style: overlayButton, title: "Group the checked rows", onClick: groupSelectedNodes, children: [_jsx(RectangleGroupIcon, { style: iconStyle(14) }), "Group (", groupSelection.size, ")"] }))] }), _jsx("div", { ref: bodyRef, style: { position: 'relative', flex: 1, minHeight: 0 }, children: !playbackEnabled ? (_jsx("div", { style: {
239
+ }, children: [(showTransport || groupSelection.size >= 2) && (_jsxs("div", { style: {
240
+ display: 'flex',
241
+ alignItems: 'center',
242
+ gap: 6,
243
+ padding: '6px 10px',
244
+ borderBottom: `1px solid ${theme.border}`,
245
+ }, children: [showTransport && (_jsxs(_Fragment, { children: [_jsx("button", { className: "qe-hover", style: { ...overlayButton, opacity: playbackEnabled ? 1 : 0.45 }, title: stagePlaying ? 'Pause stage preview' : 'Play stage preview', disabled: !playbackEnabled, onClick: handleStagePlayPause, children: stagePlaying ? (_jsx(PauseIcon, { style: iconStyle(14) })) : (_jsx(PlayIcon, { style: iconStyle(14) })) }), _jsx("button", { className: "qe-hover", style: { ...overlayButton, opacity: playbackEnabled ? 1 : 0.45 }, title: "Step one frame (stage)", disabled: !playbackEnabled || stagePlaying, onClick: () => (playbackRef.current.stepQueued = true), children: _jsx(ForwardIcon, { style: iconStyle(14) }) }), _jsx("button", { className: "qe-hover", style: { ...overlayButton, opacity: playbackEnabled ? 1 : 0.45 }, title: "Restart focused effect only", disabled: !playbackEnabled, onClick: restartFocus, children: _jsx(ArrowPathIcon, { style: iconStyle(14) }) }), _jsx("select", { className: "qe-hover", style: { ...overlayButton, padding: '5px 4px', opacity: playbackEnabled ? 1 : 0.45 }, title: "Playback speed", value: speed, disabled: !playbackEnabled, onChange: (e) => {
246
+ const next = Number(e.target.value);
247
+ playbackRef.current.speed = next;
248
+ setSpeed(next);
249
+ }, children: PLAYBACK_SPEEDS.map((s) => (_jsxs("option", { value: s, children: ["\u00D7", s] }, s))) }), _jsx("span", { ref: counterRef, style: {
250
+ fontSize: 12,
251
+ color: theme.textDim,
252
+ fontVariantNumeric: 'tabular-nums',
253
+ paddingLeft: 4,
254
+ minWidth: 120,
255
+ } })] })), groupSelection.size >= 2 && (_jsxs("button", { className: "qe-hover", style: overlayButton, title: "Group the checked rows", onClick: groupSelectedNodes, children: [_jsx(RectangleGroupIcon, { style: iconStyle(14) }), "Group (", groupSelection.size, ")"] }))] })), _jsx("div", { ref: bodyRef, style: { position: 'relative', flex: 1, minHeight: 0 }, children: !playbackEnabled ? (_jsx("div", { style: {
254
256
  position: 'absolute',
255
257
  inset: 0,
256
258
  display: 'flex',
@@ -261,7 +263,7 @@ export function TimelinePanel(props) {
261
263
  fontSize: 13,
262
264
  color: theme.textDim,
263
265
  lineHeight: 1.5,
264
- }, children: "Selected effect is not in the scene. Drag it into the viewport to preview playback." })) : (_jsxs(_Fragment, { children: [_jsxs("div", { style: {
266
+ }, children: "The focused effect is hidden. Show it in the Scene list to preview playback." })) : (_jsxs(_Fragment, { children: [_jsxs("div", { style: {
265
267
  position: 'absolute',
266
268
  top: 0,
267
269
  left: 0,
@@ -271,7 +273,12 @@ export function TimelinePanel(props) {
271
273
  gridTemplateColumns: `${LABEL_WIDTH}px 1fr`,
272
274
  background: theme.panelBg,
273
275
  zIndex: 2,
274
- }, children: [_jsx("div", { style: { height: RULER_HEIGHT } }), _jsx("div", { className: "qe-hover-bg", style: { position: 'relative', height: RULER_HEIGHT, cursor: 'ew-resize', overflow: 'hidden' }, onPointerDown: (e) => {
276
+ }, children: [_jsx("div", { style: { height: RULER_HEIGHT } }), _jsx("div", { className: "qe-hover-bg", style: {
277
+ position: 'relative',
278
+ height: RULER_HEIGHT,
279
+ cursor: 'ew-resize',
280
+ overflow: 'hidden',
281
+ }, onPointerDown: (e) => {
275
282
  playbackRef.current.scrubbing = true;
276
283
  e.target.setPointerCapture(e.pointerId);
277
284
  queueScrub(e.clientX);
@@ -281,7 +288,16 @@ export function TimelinePanel(props) {
281
288
  }, onPointerUp: (e) => {
282
289
  playbackRef.current.scrubbing = false;
283
290
  e.target.releasePointerCapture?.(e.pointerId);
284
- }, children: ticks.map((t) => (_jsxs("div", { style: { position: 'absolute', left: TIMELINE_INSET + t * pxPerSecond, top: 0, bottom: 0, borderLeft: `1px solid ${theme.border}`, fontSize: 10, color: theme.textDim, paddingLeft: 3 }, children: [t, "s"] }, t))) })] }), _jsx("div", { style: {
291
+ }, children: ticks.map((t) => (_jsxs("div", { style: {
292
+ position: 'absolute',
293
+ left: TIMELINE_INSET + t * pxPerSecond,
294
+ top: 0,
295
+ bottom: 0,
296
+ borderLeft: `1px solid ${theme.border}`,
297
+ fontSize: 10,
298
+ color: theme.textDim,
299
+ paddingLeft: 3,
300
+ }, children: [t, "s"] }, t))) })] }), _jsx("div", { style: {
285
301
  position: 'absolute',
286
302
  top: RULER_HEIGHT,
287
303
  left: 0,
@@ -293,7 +309,16 @@ export function TimelinePanel(props) {
293
309
  display: 'grid',
294
310
  gridTemplateColumns: `${LABEL_WIDTH}px 1fr`,
295
311
  gridAutoRows: 'min-content',
296
- }, children: visibleRows.map((row) => (_jsx(TimelineTrackRow, { row: row, binding: binding, selected: row.node === selectedNode, onSelectNode: onSelectNode, pxPerSecond: pxPerSecond, offsetPx: TIMELINE_INSET, collapsed: row.kind === 'group' && collapsedGroups.has(row.node), onToggleCollapse: row.kind === 'group' ? () => toggleCollapse(row.node) : undefined, hidden: row.kind === 'track' && hiddenSystems.has(row.system), onToggleVisible: row.kind === 'track' ? () => toggleVisible(row.system) : undefined, groupSelected: groupSelection.has(row.node), onToggleGroupSelect: () => toggleGroupSelect(row.node) }, row.node.uniqueId))) }) }), _jsx("div", { style: { position: 'absolute', top: 0, bottom: 0, left: LABEL_WIDTH, width: 1, background: theme.border, pointerEvents: 'none', zIndex: 3 } }), _jsx("div", { ref: playheadRef, style: {
312
+ }, children: visibleRows.map((row) => (_jsx(TimelineTrackRow, { row: row, binding: binding, selected: row.node === selectedNode, onSelectNode: onSelectNode, pxPerSecond: pxPerSecond, offsetPx: TIMELINE_INSET, collapsed: row.kind === 'group' && collapsedGroups.has(row.node), onToggleCollapse: row.kind === 'group' ? () => toggleCollapse(row.node) : undefined, hidden: row.kind === 'track' && hiddenSystems.has(row.system), onToggleVisible: row.kind === 'track' ? () => toggleVisible(row.system) : undefined, groupSelected: groupSelection.has(row.node), onToggleGroupSelect: () => toggleGroupSelect(row.node) }, row.node.uniqueId))) }) }), _jsx("div", { style: {
313
+ position: 'absolute',
314
+ top: 0,
315
+ bottom: 0,
316
+ left: LABEL_WIDTH,
317
+ width: 1,
318
+ background: theme.border,
319
+ pointerEvents: 'none',
320
+ zIndex: 3,
321
+ } }), _jsx("div", { ref: playheadRef, style: {
297
322
  position: 'absolute',
298
323
  top: 0,
299
324
  bottom: 0,
@@ -1,5 +1,5 @@
1
- import React from 'react';
2
1
  import type { TransformNode } from '@babylonjs/core/Meshes/transformNode';
2
+ import React from 'react';
3
3
  import type { EffectBinding } from '../core/binding';
4
4
  import type { TimelineRow } from '../core/timeline';
5
5
  export interface TimelineTrackRowProps {
@@ -1,9 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useState } from 'react';
3
2
  import { CheckIcon, ChevronDownIcon, ChevronRightIcon, EyeIcon, EyeSlashIcon, PencilSquareIcon, PlusIcon, SparklesIcon, SquaresPlusIcon, XMarkIcon, } from '@heroicons/react/24/solid';
3
+ import { useState } from 'react';
4
4
  import { buildEffectTree, collectSystems } from '../core/effectTree';
5
- import { createChildSystem, removeSystems } from '../core/systems';
6
5
  import { ungroupNode } from '../core/groups';
6
+ import { createChildSystem, removeSystems } from '../core/systems';
7
7
  import { PromptDialog } from './PromptDialog';
8
8
  import { iconStyle } from './icons';
9
9
  import { theme } from './theme';
@@ -20,7 +20,7 @@ const ROW_HEIGHT = 28;
20
20
  const checkboxButton = { ...iconButton, width: 12, height: 12 };
21
21
  /** One row of the timeline: a label cell (name + actions) and, for tracks, a bar cell. */
22
22
  export function TimelineTrackRow(props) {
23
- const { row, binding, selected, onSelectNode, pxPerSecond, offsetPx, collapsed, onToggleCollapse, hidden, onToggleVisible, groupSelected, onToggleGroupSelect } = props;
23
+ const { row, binding, selected, onSelectNode, pxPerSecond, offsetPx, collapsed, onToggleCollapse, hidden, onToggleVisible, groupSelected, onToggleGroupSelect, } = props;
24
24
  const [renaming, setRenaming] = useState(false);
25
25
  const isRoot = row.node === binding.root;
26
26
  const labelStyle = {
@@ -44,10 +44,17 @@ export function TimelineTrackRow(props) {
44
44
  return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "qe-hover-bg", style: { ...labelStyle, borderTop: `1px solid ${theme.border}` }, onClick: () => onSelectNode(row.node), children: [!isRoot && (_jsx("button", { className: "qe-hover", title: groupSelected ? 'Remove from group selection' : 'Select for grouping', style: { ...checkboxButton, color: groupSelected ? theme.accent : theme.textDim }, onClick: (e) => {
45
45
  e.stopPropagation();
46
46
  onToggleGroupSelect();
47
- }, children: groupSelected ? (_jsx(CheckIcon, { style: iconStyle(12) })) : (_jsx("span", { style: { width: 10, height: 10, border: `1px solid currentColor`, borderRadius: 2, display: 'block', boxSizing: 'border-box' } })) })), _jsx("span", { className: "qe-hover", title: collapsed ? 'Expand' : 'Collapse', style: { color: theme.textDim, width: 12, cursor: 'pointer', display: 'inline-flex' }, onClick: (e) => {
47
+ }, children: groupSelected ? (_jsx(CheckIcon, { style: iconStyle(12) })) : (_jsx("span", { style: {
48
+ width: 10,
49
+ height: 10,
50
+ border: `1px solid currentColor`,
51
+ borderRadius: 2,
52
+ display: 'block',
53
+ boxSizing: 'border-box',
54
+ } })) })), _jsx("span", { className: "qe-hover", title: collapsed ? 'Expand' : 'Collapse', style: { color: theme.textDim, width: 12, cursor: 'pointer', display: 'inline-flex' }, onClick: (e) => {
48
55
  e.stopPropagation();
49
56
  onToggleCollapse?.();
50
- }, children: collapsed ? _jsx(ChevronRightIcon, { style: iconStyle(12) }) : _jsx(ChevronDownIcon, { style: iconStyle(12) }) }), _jsx("span", { style: { flex: 1, overflow: 'hidden', textOverflow: 'ellipsis' }, children: row.name }), _jsx("button", { className: "qe-hover", title: "Add system to this group", style: { ...iconButton, color: theme.accent }, onClick: (e) => {
57
+ }, children: collapsed ? (_jsx(ChevronRightIcon, { style: iconStyle(12) })) : (_jsx(ChevronDownIcon, { style: iconStyle(12) })) }), _jsx("span", { style: { flex: 1, overflow: 'hidden', textOverflow: 'ellipsis' }, children: row.name }), _jsx("button", { className: "qe-hover", title: "Add system to this group", style: { ...iconButton, color: theme.accent }, onClick: (e) => {
51
58
  e.stopPropagation();
52
59
  let created;
53
60
  binding.apply(() => {
@@ -82,7 +89,14 @@ export function TimelineTrackRow(props) {
82
89
  return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "qe-hover-bg", style: { ...labelStyle, opacity: hidden ? 0.5 : 1 }, onClick: () => onSelectNode(row.node), children: [!isRoot && (_jsx("button", { className: "qe-hover", title: groupSelected ? 'Remove from group selection' : 'Select for grouping', style: { ...checkboxButton, color: groupSelected ? theme.accent : theme.textDim }, onClick: (e) => {
83
90
  e.stopPropagation();
84
91
  onToggleGroupSelect();
85
- }, children: groupSelected ? (_jsx(CheckIcon, { style: iconStyle(12) })) : (_jsx("span", { style: { width: 10, height: 10, border: `1px solid currentColor`, borderRadius: 2, display: 'block', boxSizing: 'border-box' } })) })), _jsx("button", { className: "qe-hover", title: hidden ? 'Show in viewport' : 'Hide in viewport', style: { ...iconButton, color: hidden ? theme.textDim : theme.accent }, onClick: (e) => {
92
+ }, children: groupSelected ? (_jsx(CheckIcon, { style: iconStyle(12) })) : (_jsx("span", { style: {
93
+ width: 10,
94
+ height: 10,
95
+ border: `1px solid currentColor`,
96
+ borderRadius: 2,
97
+ display: 'block',
98
+ boxSizing: 'border-box',
99
+ } })) })), _jsx("button", { className: "qe-hover", title: hidden ? 'Show in viewport' : 'Hide in viewport', style: { ...iconButton, color: hidden ? theme.textDim : theme.accent }, onClick: (e) => {
86
100
  e.stopPropagation();
87
101
  onToggleVisible?.();
88
102
  }, children: hidden ? _jsx(EyeSlashIcon, { style: iconStyle(12) }) : _jsx(EyeIcon, { style: iconStyle(12) }) }), _jsx("span", { style: { color: theme.accent, display: 'inline-flex' }, children: _jsx(SparklesIcon, { style: iconStyle(11) }) }), _jsxs("span", { style: { flex: 1, overflow: 'hidden', textOverflow: 'ellipsis' }, children: [row.name, system.onlyUsedByOther ? ' (sub)' : ''] }), !isRoot && (_jsx("button", { className: "qe-hover", title: "Rename", style: { ...iconButton, color: theme.textDim }, onClick: (e) => {
@@ -0,0 +1,24 @@
1
+ import type { Scene } from '@babylonjs/core/scene';
2
+ import type { BatchedRenderer } from 'babylon.quarks';
3
+ import React from 'react';
4
+ import type { EffectBinding } from '../core/binding';
5
+ import type { PlaybackState } from '../core/playback';
6
+ export interface TransportBarProps {
7
+ binding: EffectBinding | null;
8
+ scene: Scene;
9
+ renderer: BatchedRenderer;
10
+ playbackRef: React.MutableRefObject<PlaybackState>;
11
+ /** The host's render loop writes time and particle count here. */
12
+ counterRef: React.RefObject<HTMLSpanElement>;
13
+ stagePlaying: boolean;
14
+ setStagePlaying: (playing: boolean) => void;
15
+ speed: number;
16
+ setSpeed: (speed: number) => void;
17
+ /** False when the focused effect is hidden. */
18
+ enabled: boolean;
19
+ }
20
+ /**
21
+ * Floating playback controls in the viewport (like Unity's Particle Effect overlay), so playing
22
+ * and restarting never needs the timeline open.
23
+ */
24
+ export declare function TransportBar(props: TransportBarProps): React.ReactElement;
@@ -0,0 +1,43 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ArrowPathIcon, ForwardIcon, PauseIcon, PlayIcon } from '@heroicons/react/24/solid';
3
+ import { PLAYBACK_SPEEDS, restartFocusPlayback, toggleStagePlayback } from '../core/transport';
4
+ import { iconStyle } from './icons';
5
+ import { buttonStyle, theme } from './theme';
6
+ const barButton = {
7
+ ...buttonStyle,
8
+ width: 28,
9
+ height: 26,
10
+ padding: 0,
11
+ display: 'inline-flex',
12
+ alignItems: 'center',
13
+ justifyContent: 'center',
14
+ };
15
+ /**
16
+ * Floating playback controls in the viewport (like Unity's Particle Effect overlay), so playing
17
+ * and restarting never needs the timeline open.
18
+ */
19
+ export function TransportBar(props) {
20
+ const { binding, scene, renderer, playbackRef, counterRef, stagePlaying, setStagePlaying, speed, setSpeed } = props;
21
+ const enabled = props.enabled && binding !== null;
22
+ return (_jsxs("div", { style: {
23
+ display: 'flex',
24
+ alignItems: 'center',
25
+ gap: 4,
26
+ padding: 4,
27
+ background: theme.panelBg,
28
+ border: `1px solid ${theme.border}`,
29
+ borderRadius: 8,
30
+ fontFamily: theme.font,
31
+ }, children: [_jsx("button", { className: "qe-hover", style: barButton, title: stagePlaying ? 'Pause (Space)' : 'Play (Space)', disabled: !enabled, onClick: () => toggleStagePlayback(binding, scene, playbackRef.current, stagePlaying, setStagePlaying), children: stagePlaying ? _jsx(PauseIcon, { style: iconStyle(14) }) : _jsx(PlayIcon, { style: iconStyle(14) }) }), _jsx("button", { className: "qe-hover", style: barButton, title: "Step one frame", disabled: !enabled || stagePlaying, onClick: () => (playbackRef.current.stepQueued = true), children: _jsx(ForwardIcon, { style: iconStyle(14) }) }), _jsx("button", { className: "qe-hover", style: barButton, title: "Restart the focused effect", disabled: !enabled, onClick: () => binding && restartFocusPlayback(binding, renderer, scene, playbackRef.current), children: _jsx(ArrowPathIcon, { style: iconStyle(14) }) }), _jsx("select", { className: "qe-hover", style: { ...barButton, width: 'auto', padding: '0 4px' }, title: "Playback speed", value: speed, disabled: !enabled, onChange: (e) => {
32
+ const next = Number(e.target.value);
33
+ playbackRef.current.speed = next;
34
+ setSpeed(next);
35
+ }, children: PLAYBACK_SPEEDS.map((s) => (_jsxs("option", { value: s, children: ["\u00D7", s] }, s))) }), _jsx("span", { ref: counterRef, style: {
36
+ fontSize: 12,
37
+ color: theme.textDim,
38
+ fontVariantNumeric: 'tabular-nums',
39
+ padding: '0 6px',
40
+ minWidth: 150,
41
+ whiteSpace: 'nowrap',
42
+ } })] }));
43
+ }
@@ -1,4 +1,3 @@
1
- import React from 'react';
2
1
  import { ScalarGenerator } from '../core/values';
3
2
  /**
4
3
  * Unity-like value field: Constant | Random Between Two Constants | Curve,
@@ -12,4 +11,4 @@ export declare function ValueField(props: {
12
11
  onChange: (generator: ScalarGenerator) => void;
13
12
  min?: number;
14
13
  curveMax?: number;
15
- }): React.JSX.Element;
14
+ }): import("react").JSX.Element;
@@ -1,12 +1,11 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { ApplyCollision, ApplyForce, ChangeEmitDirection, ColorBySpeed, ConstantValue, ForceOverLife, FrameOverLife, Gradient, InheritVelocity, IntervalValue, LimitSpeedOverLife, Noise, OrbitOverLife, RotationBySpeed, RotationOverLife, SizeBySpeed, SpeedOverLife, TurbulenceField, Vector3, VelocityOverLife, WidthOverLength, } from 'babylon.quarks';
3
- import { ensureGroundResolver } from '../core/collision';
4
- import { DEFAULT_GRADIENT_STOPS, buildGradient, findBehavior, readGradientStops } from '../core/colors';
2
+ import { ApplyCollision, ApplyForce, ChangeEmitDirection, ColorBySpeed, ConstantValue, ForceOverLife, FrameOverLife, getPhysicsResolver, Gradient, InheritVelocity, IntervalValue, LimitSpeedOverLife, Noise, OrbitOverLife, RotationBySpeed, RotationOverLife, SizeBySpeed, SpeedOverLife, TurbulenceField, Vector3, VelocityOverLife, WidthOverLength, } from 'babylon.quarks';
3
+ import { buildGradient, DEFAULT_GRADIENT_STOPS, findBehavior, readGradientStops } from '../core/colors';
5
4
  import { buildCurve, buildScalar, readPieces, readScalar } from '../core/values';
6
- import { GradientEditor } from './GradientEditor';
7
5
  import { CurveEditor } from './CurveEditor';
8
- import { ModuleSection } from './ModuleSection';
9
6
  import { NumberField, Row, SelectField } from './fields';
7
+ import { GradientEditor } from './GradientEditor';
8
+ import { ModuleSection } from './ModuleSection';
10
9
  import { ValueField } from './ValueField';
11
10
  function removeBehaviorOfType(behaviors, type) {
12
11
  for (let i = behaviors.length - 1; i >= 0; i--) {
@@ -71,17 +70,18 @@ export function EmitDirectionModule({ binding }) {
71
70
  return (_jsx(BehaviorModule, { binding: binding, title: "Randomize Direction", type: "ChangeEmitDirection", create: () => new ChangeEmitDirection(new IntervalValue(0, Math.PI / 6)), children: (behavior) => (_jsx(ValueField, { label: "Angle (rad)", generator: behavior.angle, min: 0, curveMax: Math.PI, onChange: (g) => binding.apply(() => (behavior.angle = g)) })) }));
72
71
  }
73
72
  export function CollisionModule({ binding }) {
74
- // Ensure the shared ground-plane resolver exists before any ApplyCollision is created,
75
- // otherwise its update() would dereference an undefined resolver.
76
- const resolver = ensureGroundResolver();
77
- return (_jsx(BehaviorModule, { binding: binding, title: "Collision", type: "ApplyCollision", create: () => new ApplyCollision(ensureGroundResolver(), 0.5), children: (behavior) => (_jsxs(_Fragment, { children: [_jsx(Row, { label: "Bounce", children: _jsx(NumberField, { value: behavior.bounce, min: 0, step: 0.05, onChange: (v) => binding.apply(() => (behavior.bounce = v)) }) }), _jsx(Row, { label: "Floor Y", children: _jsx(NumberField, { value: resolver.y, step: 0.1, onChange: (v) => binding.apply(() => (resolver.y = v)) }) }), _jsx("div", { style: { fontSize: 11, opacity: 0.6, marginTop: 4 }, children: "Collides against a ground plane. Bounce is saved with the effect; Floor Y is an editor preview aid (a host can register its own colliders)." })] })) }));
73
+ // Like Unity: Collision stores bounce only. Colliders are host-provided via
74
+ // setPhysicsResolver — the editor never invents a ground plane.
75
+ return (_jsx(BehaviorModule, { binding: binding, title: "Collision", type: "ApplyCollision", create: () => new ApplyCollision(getPhysicsResolver(), 0.5), children: (behavior) => (_jsxs(_Fragment, { children: [_jsx(Row, { label: "Bounce", children: _jsx(NumberField, { value: behavior.bounce, min: 0, step: 0.05, onChange: (v) => binding.apply(() => (behavior.bounce = v)) }) }), _jsx("div", { style: { fontSize: 11, opacity: 0.6, marginTop: 4 }, children: "Bounce is saved with the effect. Colliders come from the host (setPhysicsResolver) \u2014 without one, Collision is inert." })] })) }));
78
76
  }
79
77
  /** Shared speed-range rows for the "by Speed" trio. */
80
78
  function SpeedRangeRows({ binding, behavior }) {
81
79
  return (_jsx(Row, { label: "Speed range", children: _jsxs("div", { style: { display: 'flex', gap: 6 }, children: [_jsx(NumberField, { value: behavior.speedRange.a, min: 0, onChange: (v) => binding.apply(() => (behavior.speedRange = new IntervalValue(v, behavior.speedRange.b))) }), _jsx(NumberField, { value: behavior.speedRange.b, min: 0, onChange: (v) => binding.apply(() => (behavior.speedRange = new IntervalValue(behavior.speedRange.a, v))) })] }) }));
82
80
  }
83
81
  export function ColorBySpeedModule({ binding }) {
84
- return (_jsx(BehaviorModule, { binding: binding, title: "Color by Speed", type: "ColorBySpeed", create: () => new ColorBySpeed(buildGradient(DEFAULT_GRADIENT_STOPS), new IntervalValue(0, 5)), children: (behavior) => (_jsxs(_Fragment, { children: [_jsx("div", { style: { marginTop: 6 }, children: _jsx(GradientEditor, { stops: behavior.color instanceof Gradient ? readGradientStops(behavior.color) : DEFAULT_GRADIENT_STOPS, onChange: (stops) => binding.apply(() => (behavior.color = buildGradient(stops))) }) }), _jsx(SpeedRangeRows, { binding: binding, behavior: behavior })] })) }));
82
+ return (_jsx(BehaviorModule, { binding: binding, title: "Color by Speed", type: "ColorBySpeed", create: () => new ColorBySpeed(buildGradient(DEFAULT_GRADIENT_STOPS), new IntervalValue(0, 5)), children: (behavior) => (_jsxs(_Fragment, { children: [_jsx("div", { style: { marginTop: 6 }, children: _jsx(GradientEditor, { stops: behavior.color instanceof Gradient
83
+ ? readGradientStops(behavior.color)
84
+ : DEFAULT_GRADIENT_STOPS, onChange: (stops) => binding.apply(() => (behavior.color = buildGradient(stops))) }) }), _jsx(SpeedRangeRows, { binding: binding, behavior: behavior })] })) }));
85
85
  }
86
86
  export function SizeBySpeedModule({ binding }) {
87
87
  return (_jsx(BehaviorModule, { binding: binding, title: "Size by Speed", type: "SizeBySpeed", create: () => new SizeBySpeed(buildScalar({ mode: 'curve', value: 1, min: 0, max: 1, curve: [0.5, 0.8, 1.2, 1.5] }), new IntervalValue(0, 5)), children: (behavior) => (_jsxs(_Fragment, { children: [_jsx("div", { style: { marginTop: 6 }, children: _jsx(CurveEditor, { pieces: readPieces(behavior.size), maxValue: 2, onChange: (pieces) => binding.apply(() => (behavior.size = buildCurve(pieces))) }) }), _jsx(SpeedRangeRows, { binding: binding, behavior: behavior })] })) }));
@@ -106,7 +106,13 @@ export function TextureSheetModule({ binding }) {
106
106
  const startIdx = Math.min(Math.max(0, Math.round(readScalar(system.startTileIndex).value)), tiles - 1);
107
107
  const startCol = startIdx % u;
108
108
  const startRow = Math.floor(startIdx / u);
109
- return (_jsxs(ModuleSection, { title: "Texture Sheet Animation", defaultOpen: false, children: [textureUrl && (_jsxs("div", { style: { marginTop: 6 }, children: [_jsxs("div", { style: { position: 'relative', width: 218, border: '1px solid #2b3761', borderRadius: 6, overflow: 'hidden' }, children: [_jsx("img", { src: textureUrl, alt: "texture atlas", style: { width: '100%', display: 'block' } }), _jsx("div", { style: {
109
+ return (_jsxs(ModuleSection, { title: "Texture Sheet Animation", defaultOpen: false, children: [textureUrl && (_jsxs("div", { style: { marginTop: 6 }, children: [_jsxs("div", { style: {
110
+ position: 'relative',
111
+ width: 218,
112
+ border: '1px solid #2b3761',
113
+ borderRadius: 6,
114
+ overflow: 'hidden',
115
+ }, children: [_jsx("img", { src: textureUrl, alt: "texture atlas", style: { width: '100%', display: 'block' } }), _jsx("div", { style: {
110
116
  position: 'absolute',
111
117
  inset: 0,
112
118
  pointerEvents: 'none',
@@ -133,7 +139,13 @@ export function TextureSheetModule({ binding }) {
133
139
  }, children: startIdx }) })] }), _jsxs("div", { style: { fontSize: 11, color: '#b7c6ea', marginTop: 3 }, children: [u, " \u00D7 ", v, " = ", tiles, " tiles; frame indices 0\u2013", tiles - 1, "; start tile", ' ', _jsx("span", { style: { color: '#ffce6b', fontWeight: 600 }, children: startIdx })] })] })), _jsx(Row, { label: "Tiles U", children: _jsx(NumberField, { value: system.uTileCount, min: 1, step: 1, onChange: (v) => binding.apply((s) => (s.uTileCount = Math.round(v))) }) }), _jsx(Row, { label: "Tiles V", children: _jsx(NumberField, { value: system.vTileCount, min: 1, step: 1, onChange: (v) => binding.apply((s) => (s.vTileCount = Math.round(v))) }) }), _jsx(Row, { label: "Blend tiles", children: _jsx("input", { type: "checkbox", className: "qe-hover", checked: system.blendTiles, style: { justifySelf: 'start', width: 15, height: 15, accentColor: '#78a5ff', cursor: 'pointer' }, onChange: (e) => binding.apply((s) => (s.blendTiles = e.target.checked)) }) }), _jsx(Row, { label: "Start tile", children: _jsx(NumberField, { value: readScalar(system.startTileIndex).value, min: 0, step: 1, onChange: (v) => binding.apply((s) => (s.startTileIndex = new ConstantValue(Math.round(v)))) }) }), _jsx(Row, { label: "Animate frames", children: _jsx("input", { type: "checkbox", className: "qe-hover", checked: !!frameBehavior, style: { justifySelf: 'start', width: 15, height: 15, accentColor: '#78a5ff', cursor: 'pointer' }, onChange: (e) => binding.apply((s) => {
134
140
  removeBehaviorOfType(s.behaviors, 'FrameOverLife');
135
141
  if (e.target.checked) {
136
- s.addBehavior(new FrameOverLife(buildScalar({ mode: 'curve', value: 0, min: 0, max: tiles - 1, curve: [0, (tiles - 1) / 3, (2 * (tiles - 1)) / 3, tiles - 1] })));
142
+ s.addBehavior(new FrameOverLife(buildScalar({
143
+ mode: 'curve',
144
+ value: 0,
145
+ min: 0,
146
+ max: tiles - 1,
147
+ curve: [0, (tiles - 1) / 3, (2 * (tiles - 1)) / 3, tiles - 1],
148
+ })));
137
149
  }
138
150
  }) }) }), frameBehavior && (_jsx("div", { style: { marginTop: 6 }, children: _jsx(CurveEditor, { pieces: readPieces(frameBehavior.frame), maxValue: Math.max(1, tiles - 1), onChange: (pieces) => binding.apply(() => (frameBehavior.frame = buildCurve(pieces))) }) }))] }));
139
151
  }
@@ -1,24 +1,38 @@
1
+ export { AssetsPanel } from './AssetsPanel';
2
+ export type { AssetsPanelProps } from './AssetsPanel';
3
+ export { CollisionModule, ColorBySpeedModule, EmitDirectionModule, ForceOverLifeModule, GravityModule, LimitSpeedOverLifeModule, NoiseModule, OrbitOverLifeModule, RotationBySpeedModule, RotationOverLifeModule, SizeBySpeedModule, SpeedOverLifeModule, TextureSheetModule, TurbulenceModule, WidthOverTrailModule, } from './behaviorModules';
4
+ export { ChoiceDialog } from './ChoiceDialog';
5
+ export type { ChoiceDialogAction, ChoiceDialogProps } from './ChoiceDialog';
6
+ export { ContextMenu } from './ContextMenu';
7
+ export type { ContextMenuItem, ContextMenuProps } from './ContextMenu';
8
+ export { CurveEditor } from './CurveEditor';
9
+ export { EditorWorkspace, QuarksEditorWorkspace } from './EditorWorkspace';
10
+ export type { EditorWorkspaceProps } from './EditorWorkspace';
1
11
  export { EffectEditor } from './EffectEditor';
2
12
  export type { EffectEditorProps } from './EffectEditor';
3
- export { SpeedOverLifeModule, LimitSpeedOverLifeModule, ForceOverLifeModule, GravityModule, RotationOverLifeModule, NoiseModule, TurbulenceModule, CollisionModule, EmitDirectionModule, TextureSheetModule, ColorBySpeedModule, SizeBySpeedModule, RotationBySpeedModule, OrbitOverLifeModule, WidthOverTrailModule, } from './behaviorModules';
4
- export type { TextureOption, GeometryOption, GeometryData } from './modules';
13
+ export { EffectEditorHost, QuarksEffectEditor } from './EffectEditorHost';
14
+ export type { EffectEditorHostHandle, EffectEditorHostProps } from './EffectEditorHost';
15
+ export { EffectTreeView } from './EffectTreeView';
16
+ export type { EffectTreeViewProps } from './EffectTreeView';
17
+ export { CheckboxField, NumberField, Row, SelectField } from './fields';
18
+ export { GradientEditor } from './GradientEditor';
19
+ export { MessageDialog } from './MessageDialog';
20
+ export type { MessageDialogProps } from './MessageDialog';
21
+ export { ColorOverLifeModule, EmissionModule, MainModule, RendererModule, ShapeModule, SizeOverLifeModule, } from './modules';
22
+ export type { GeometryData, GeometryOption, TextureOption } from './modules';
23
+ export { ModuleSection } from './ModuleSection';
24
+ export { ProjectsPage } from './ProjectsPage';
25
+ export type { ProjectsPageProps } from './ProjectsPage';
26
+ export { PromptDialog } from './PromptDialog';
27
+ export type { PromptDialogProps } from './PromptDialog';
28
+ export { ScenePanel } from './ScenePanel';
29
+ export type { ScenePanelProps } from './ScenePanel';
5
30
  export { SubEmittersModule } from './SubEmittersModule';
31
+ export { buttonStyle, inputStyle, labelStyle, rowStyle, theme } from './theme';
6
32
  export { TimelinePanel } from './TimelinePanel';
7
- export type { TimelinePanelProps, PlaybackState } from './TimelinePanel';
33
+ export type { PlaybackState, TimelinePanelProps } from './TimelinePanel';
8
34
  export { TimelineTrackRow } from './TimelineTrackRow';
9
35
  export type { TimelineTrackRowProps } from './TimelineTrackRow';
10
- export { ModuleSection } from './ModuleSection';
11
- export { PromptDialog } from './PromptDialog';
12
- export type { PromptDialogProps } from './PromptDialog';
13
- export { CurveEditor } from './CurveEditor';
14
- export { GradientEditor } from './GradientEditor';
36
+ export { TransportBar } from './TransportBar';
37
+ export type { TransportBarProps } from './TransportBar';
15
38
  export { ValueField } from './ValueField';
16
- export { Row, NumberField, CheckboxField, SelectField } from './fields';
17
- export { MainModule, EmissionModule, ShapeModule, SizeOverLifeModule, ColorOverLifeModule, RendererModule, } from './modules';
18
- export { theme, inputStyle, rowStyle, labelStyle, buttonStyle } from './theme';
19
- export { MessageDialog } from './MessageDialog';
20
- export type { MessageDialogProps } from './MessageDialog';
21
- export { GalleryPanel } from './GalleryPanel';
22
- export type { GalleryPanelProps } from './GalleryPanel';
23
- export { EffectEditorHost, QuarksEffectEditor } from './EffectEditorHost';
24
- export type { EffectEditorHostProps, EffectEditorHostHandle } from './EffectEditorHost';
@@ -1,16 +1,23 @@
1
+ export { AssetsPanel } from './AssetsPanel';
2
+ export { CollisionModule, ColorBySpeedModule, EmitDirectionModule, ForceOverLifeModule, GravityModule, LimitSpeedOverLifeModule, NoiseModule, OrbitOverLifeModule, RotationBySpeedModule, RotationOverLifeModule, SizeBySpeedModule, SpeedOverLifeModule, TextureSheetModule, TurbulenceModule, WidthOverTrailModule, } from './behaviorModules';
3
+ export { ChoiceDialog } from './ChoiceDialog';
4
+ export { ContextMenu } from './ContextMenu';
5
+ export { CurveEditor } from './CurveEditor';
6
+ export { EditorWorkspace, QuarksEditorWorkspace } from './EditorWorkspace';
1
7
  export { EffectEditor } from './EffectEditor';
2
- export { SpeedOverLifeModule, LimitSpeedOverLifeModule, ForceOverLifeModule, GravityModule, RotationOverLifeModule, NoiseModule, TurbulenceModule, CollisionModule, EmitDirectionModule, TextureSheetModule, ColorBySpeedModule, SizeBySpeedModule, RotationBySpeedModule, OrbitOverLifeModule, WidthOverTrailModule, } from './behaviorModules';
8
+ export { EffectEditorHost, QuarksEffectEditor } from './EffectEditorHost';
9
+ export { EffectTreeView } from './EffectTreeView';
10
+ export { CheckboxField, NumberField, Row, SelectField } from './fields';
11
+ export { GradientEditor } from './GradientEditor';
12
+ export { MessageDialog } from './MessageDialog';
13
+ export { ColorOverLifeModule, EmissionModule, MainModule, RendererModule, ShapeModule, SizeOverLifeModule, } from './modules';
14
+ export { ModuleSection } from './ModuleSection';
15
+ export { ProjectsPage } from './ProjectsPage';
16
+ export { PromptDialog } from './PromptDialog';
17
+ export { ScenePanel } from './ScenePanel';
3
18
  export { SubEmittersModule } from './SubEmittersModule';
19
+ export { buttonStyle, inputStyle, labelStyle, rowStyle, theme } from './theme';
4
20
  export { TimelinePanel } from './TimelinePanel';
5
21
  export { TimelineTrackRow } from './TimelineTrackRow';
6
- export { ModuleSection } from './ModuleSection';
7
- export { PromptDialog } from './PromptDialog';
8
- export { CurveEditor } from './CurveEditor';
9
- export { GradientEditor } from './GradientEditor';
22
+ export { TransportBar } from './TransportBar';
10
23
  export { ValueField } from './ValueField';
11
- export { Row, NumberField, CheckboxField, SelectField } from './fields';
12
- export { MainModule, EmissionModule, ShapeModule, SizeOverLifeModule, ColorOverLifeModule, RendererModule, } from './modules';
13
- export { theme, inputStyle, rowStyle, labelStyle, buttonStyle } from './theme';
14
- export { MessageDialog } from './MessageDialog';
15
- export { GalleryPanel } from './GalleryPanel';
16
- export { EffectEditorHost, QuarksEffectEditor } from './EffectEditorHost';
@@ -117,8 +117,7 @@ const INSPECTOR_HINTS = {
117
117
  'turbulence.strength': 'Per-axis multiplier for turbulent velocity.',
118
118
  'Time scale': 'How fast the turbulence field evolves over time (per axis).',
119
119
  'Angle (rad)': 'Maximum random deflection from the base emit direction, in radians.',
120
- Bounce: 'Energy retained after hitting the ground (0 = no bounce, 1 = full bounce).',
121
- 'Floor Y': 'World-space Y height of the preview ground plane used for collision.',
120
+ Bounce: 'Energy retained after a collision (0 = stick, 1 = full bounce). Colliders are host-provided.',
122
121
  'Speed range': 'Particle speed range (min, max) that maps to the color/size/rotation curve.',
123
122
  'Orbit speed': 'Angular speed of orbit around the configured axis over lifetime.',
124
123
  'rotationOverLife.velocity': 'Angular velocity applied over normalized lifetime (rad/s).',