babylon.quarks-editor 0.17.8 → 0.18.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/CHANGELOG.md +46 -0
- package/README.md +1 -1
- package/dist/core/binding.js +2 -5
- package/dist/core/defaultEffect.d.ts +12 -0
- package/dist/core/defaultEffect.js +43 -0
- package/dist/core/editorScene.d.ts +7 -0
- package/dist/core/editorScene.js +11 -0
- package/dist/core/effectTree.js +2 -1
- package/dist/core/emitterShapeWireframe.d.ts +20 -0
- package/dist/core/emitterShapeWireframe.js +329 -0
- package/dist/core/galleryDragSession.d.ts +4 -0
- package/dist/core/galleryDragSession.js +8 -0
- package/dist/core/galleryDropMarker.d.ts +13 -0
- package/dist/core/galleryDropMarker.js +42 -0
- package/dist/core/galleryLayout.d.ts +20 -0
- package/dist/core/galleryLayout.js +68 -0
- package/dist/core/galleryPlayback.d.ts +14 -0
- package/dist/core/galleryPlayback.js +72 -0
- package/dist/core/galleryScene.d.ts +20 -0
- package/dist/core/galleryScene.js +65 -0
- package/dist/core/gallerySelectionMarker.d.ts +25 -0
- package/dist/core/gallerySelectionMarker.js +69 -0
- package/dist/core/galleryTree.d.ts +49 -0
- package/dist/core/galleryTree.js +138 -0
- package/dist/core/loadEffect.d.ts +13 -0
- package/dist/core/loadEffect.js +31 -17
- package/dist/core/loadEffectGallery.d.ts +36 -0
- package/dist/core/loadEffectGallery.js +97 -0
- package/dist/core/material.d.ts +13 -0
- package/dist/core/material.js +68 -0
- package/dist/core/pickGalleryFiles.d.ts +5 -0
- package/dist/core/pickGalleryFiles.js +42 -0
- package/dist/core/playback.d.ts +23 -0
- package/dist/core/playback.js +32 -0
- package/dist/core/renderDebug.d.ts +8 -0
- package/dist/core/renderDebug.js +42 -0
- package/dist/core/scrub.d.ts +14 -0
- package/dist/core/scrub.js +70 -0
- package/dist/core/timeline.d.ts +4 -1
- package/dist/core/timeline.js +30 -3
- package/dist/core/transformStore.d.ts +3 -0
- package/dist/core/transformStore.js +16 -0
- package/dist/core/viewportPicker.d.ts +25 -0
- package/dist/core/viewportPicker.js +86 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/react/EffectEditor.js +1 -4
- package/dist/react/EffectEditorHost.d.ts +7 -0
- package/dist/react/EffectEditorHost.js +745 -115
- package/dist/react/GalleryPanel.d.ts +20 -0
- package/dist/react/GalleryPanel.js +246 -0
- package/dist/react/InspectorInfoIcon.d.ts +7 -0
- package/dist/react/InspectorInfoIcon.js +22 -0
- package/dist/react/InspectorLabel.d.ts +7 -0
- package/dist/react/InspectorLabel.js +12 -0
- package/dist/react/MessageDialog.d.ts +9 -0
- package/dist/react/MessageDialog.js +24 -0
- package/dist/react/ModuleSection.d.ts +2 -0
- package/dist/react/ModuleSection.js +8 -1
- package/dist/react/ObjectModule.js +14 -2
- package/dist/react/TimelinePanel.d.ts +8 -11
- package/dist/react/TimelinePanel.js +129 -98
- package/dist/react/TimelineTrackRow.js +29 -15
- package/dist/react/Tooltip.d.ts +6 -0
- package/dist/react/Tooltip.js +65 -0
- package/dist/react/ValueField.d.ts +2 -0
- package/dist/react/ValueField.js +1 -1
- package/dist/react/behaviorModules.js +8 -8
- package/dist/react/fields.d.ts +2 -0
- package/dist/react/fields.js +14 -5
- package/dist/react/icons.d.ts +7 -0
- package/dist/react/icons.js +10 -0
- package/dist/react/index.d.ts +4 -0
- package/dist/react/index.js +2 -0
- package/dist/react/inspectorHints.d.ts +2 -0
- package/dist/react/inspectorHints.js +138 -0
- package/dist/react/modules.js +109 -81
- package/dist/react/theme.d.ts +2 -0
- package/dist/react/theme.js +53 -1
- package/package.json +8 -4
- package/src/core/binding.ts +2 -5
- package/src/core/defaultEffect.ts +74 -0
- package/src/core/editorScene.ts +14 -0
- package/src/core/effectTree.ts +2 -1
- package/src/core/emitterShapeWireframe.ts +373 -0
- package/src/core/galleryDragSession.ts +12 -0
- package/src/core/galleryDropMarker.ts +57 -0
- package/src/core/galleryLayout.ts +90 -0
- package/src/core/galleryPlayback.ts +90 -0
- package/src/core/galleryScene.ts +92 -0
- package/src/core/gallerySelectionMarker.ts +84 -0
- package/src/core/galleryTree.ts +187 -0
- package/src/core/loadEffect.ts +55 -18
- package/src/core/loadEffectGallery.ts +146 -0
- package/src/core/material.ts +84 -0
- package/src/core/pickGalleryFiles.ts +58 -0
- package/src/core/playback.ts +49 -0
- package/src/core/renderDebug.ts +46 -0
- package/src/core/scrub.ts +76 -1
- package/src/core/timeline.ts +34 -3
- package/src/core/transformStore.ts +19 -0
- package/src/core/viewportPicker.ts +114 -0
- package/src/index.ts +2 -0
- package/src/react/EffectEditor.tsx +5 -7
- package/src/react/EffectEditorHost.tsx +936 -142
- package/src/react/GalleryPanel.tsx +517 -0
- package/src/react/InspectorInfoIcon.tsx +36 -0
- package/src/react/InspectorLabel.tsx +18 -0
- package/src/react/MessageDialog.tsx +57 -0
- package/src/react/ModuleSection.tsx +17 -2
- package/src/react/ObjectModule.tsx +17 -6
- package/src/react/TimelinePanel.tsx +227 -174
- package/src/react/TimelineTrackRow.tsx +58 -23
- package/src/react/Tooltip.tsx +93 -0
- package/src/react/ValueField.tsx +3 -1
- package/src/react/behaviorModules.tsx +13 -8
- package/src/react/fields.tsx +15 -6
- package/src/react/icons.ts +19 -0
- package/src/react/index.ts +4 -0
- package/src/react/inspectorHints.ts +150 -0
- package/src/react/modules.tsx +198 -183
- package/src/react/theme.ts +54 -1
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
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';
|
|
3
5
|
import { groupNodes } from '../core/groups';
|
|
4
|
-
import {
|
|
6
|
+
import { pauseFocus, playFocus, scrubFocusTo, scrubFocusToSettled } from '../core/scrub';
|
|
5
7
|
import { computeTimelineRows, computeTimelineSpan } from '../core/timeline';
|
|
6
8
|
import { TimelineTrackRow } from './TimelineTrackRow';
|
|
9
|
+
import { iconStyle } from './icons';
|
|
7
10
|
import { buttonStyle, theme } from './theme';
|
|
8
11
|
const LABEL_WIDTH = 200;
|
|
9
12
|
const RULER_HEIGHT = 22;
|
|
10
|
-
const STAGE_ROW_HEIGHT = 26;
|
|
11
13
|
const PANEL_HEIGHT = 220;
|
|
12
14
|
/** Small gap between the label column and the first pixel of the time axis. */
|
|
13
15
|
export const TIMELINE_INSET = 10;
|
|
@@ -23,20 +25,18 @@ function pickTickInterval(pxPerSecond) {
|
|
|
23
25
|
return 10;
|
|
24
26
|
}
|
|
25
27
|
/**
|
|
26
|
-
* Bottom-docked
|
|
27
|
-
*
|
|
28
|
+
* Bottom-docked timeline: stage Play/Pause drives all preview effects; playhead/scrub/restart
|
|
29
|
+
* apply only to the focused effect without stopping other previews.
|
|
28
30
|
*/
|
|
29
31
|
export function TimelinePanel(props) {
|
|
30
|
-
const { binding, selectedNode, onSelectNode, scene, renderer, playbackRef, playheadRef, counterRef,
|
|
32
|
+
const { binding, selectedNode, onSelectNode, scene, renderer, playbackRef, playheadRef, counterRef, stagePlaying, setStagePlaying, speed, setSpeed, playbackEnabled = true } = props;
|
|
31
33
|
const [bodyWidthPx, setBodyWidthPx] = useState(600);
|
|
32
34
|
const [collapsedGroups, setCollapsedGroups] = useState(() => new Set());
|
|
33
35
|
const [hiddenSystems, setHiddenSystems] = useState(() => new Set());
|
|
34
36
|
const [groupSelection, setGroupSelection] = useState(() => new Set());
|
|
35
37
|
const bodyRef = useRef(null);
|
|
36
38
|
const dragRef = useRef({ raf: null, pendingTime: null });
|
|
37
|
-
|
|
38
|
-
// visibility toggles and grouping selection from the previous effect no longer refer to
|
|
39
|
-
// anything real.
|
|
39
|
+
const focusRootId = binding.root.uniqueId;
|
|
40
40
|
React.useEffect(() => {
|
|
41
41
|
setHiddenSystems(new Set());
|
|
42
42
|
setGroupSelection(new Set());
|
|
@@ -90,42 +90,35 @@ export function TimelinePanel(props) {
|
|
|
90
90
|
return () => observer.disconnect();
|
|
91
91
|
}, []);
|
|
92
92
|
const rows = useMemo(() => computeTimelineRows(binding), [binding, binding.getRevision()]);
|
|
93
|
-
// The ruler spans exactly the furthest track extent (no artificial stretch), minus a
|
|
94
|
-
// right-hand gap so the full-duration mark never sits flush against the panel edge.
|
|
95
93
|
const timelineSpan = useMemo(() => computeTimelineSpan(rows), [rows]);
|
|
96
94
|
const pxPerSecond = Math.max(1, bodyWidthPx - TIMELINE_INSET - RIGHT_PADDING) / timelineSpan;
|
|
97
|
-
// Follows playbackRef.elapsed independently of Babylon's render loop (Host only owns
|
|
98
|
-
// simulation time; this owns translating it into pixels for the playhead line). Whether
|
|
99
|
-
// the *whole* effect loops is an OR across tracks (there's no single canonical "root" —
|
|
100
|
-
// an effect can have several top-level systems): if any track loops, elapsed grows
|
|
101
|
-
// unbounded and we wrap it at timelineSpan. If none loop, finishing a run snaps playback
|
|
102
|
-
// straight back to frame 0 (paused) — a one-shot preview parked and ready for the next Play.
|
|
103
95
|
const anyLooping = useMemo(() => rows.some((r) => r.kind === 'track' && r.looping), [rows]);
|
|
104
|
-
// Tracks elapsed from the previous tick so the finish-reset below only fires on a genuine
|
|
105
|
-
// crossing during live playback. Without this, manually dragging the playhead to (or past)
|
|
106
|
-
// the end pins elapsed at timelineSpan while scrubbing=true skips the reset — but the very
|
|
107
|
-
// next tick after release would otherwise see elapsed >= timelineSpan with no memory of
|
|
108
|
-
// *how* it got there, and wrongly treat a drag as a finished run.
|
|
109
96
|
const prevElapsedRef = useRef(0);
|
|
110
97
|
React.useEffect(() => {
|
|
98
|
+
if (!playbackEnabled) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
111
101
|
let raf;
|
|
112
102
|
const tick = () => {
|
|
113
103
|
const state = playbackRef.current;
|
|
114
|
-
const elapsed = state
|
|
104
|
+
const elapsed = getFocusElapsed(state, focusRootId);
|
|
115
105
|
let shown = elapsed;
|
|
116
106
|
if (anyLooping) {
|
|
117
107
|
shown = timelineSpan > 0 ? elapsed % timelineSpan : 0;
|
|
118
108
|
}
|
|
109
|
+
else if (isFocusFinished(state, focusRootId)) {
|
|
110
|
+
// Park at real simulation time — core may finish before the ruler's worst-case span.
|
|
111
|
+
shown = elapsed;
|
|
112
|
+
}
|
|
119
113
|
else if (elapsed >= timelineSpan) {
|
|
120
114
|
shown = timelineSpan;
|
|
121
|
-
const justFinishedPlaying = !state.scrubbing &&
|
|
115
|
+
const justFinishedPlaying = !state.scrubbing && state.stagePlaying && prevElapsedRef.current < timelineSpan;
|
|
122
116
|
if (justFinishedPlaying) {
|
|
123
|
-
|
|
124
|
-
state
|
|
125
|
-
state
|
|
126
|
-
|
|
117
|
+
scrubFocusToSettled(binding, renderer, timelineSpan);
|
|
118
|
+
markFocusFinished(state, focusRootId);
|
|
119
|
+
setFocusElapsed(state, focusRootId, timelineSpan);
|
|
120
|
+
pauseFocus(binding);
|
|
127
121
|
scene.render();
|
|
128
|
-
shown = 0;
|
|
129
122
|
}
|
|
130
123
|
}
|
|
131
124
|
prevElapsedRef.current = elapsed;
|
|
@@ -136,7 +129,7 @@ export function TimelinePanel(props) {
|
|
|
136
129
|
};
|
|
137
130
|
raf = requestAnimationFrame(tick);
|
|
138
131
|
return () => cancelAnimationFrame(raf);
|
|
139
|
-
}, [pxPerSecond, timelineSpan, anyLooping, playbackRef, playheadRef,
|
|
132
|
+
}, [pxPerSecond, timelineSpan, anyLooping, playbackRef, playheadRef, focusRootId, playbackEnabled, binding, scene, renderer]);
|
|
140
133
|
const visibleRows = useMemo(() => {
|
|
141
134
|
let hideUntilDepth = null;
|
|
142
135
|
const visible = rows.filter((row) => {
|
|
@@ -170,10 +163,24 @@ export function TimelinePanel(props) {
|
|
|
170
163
|
return Math.min(timelineSpan, Math.max(0, x / pxPerSecond));
|
|
171
164
|
};
|
|
172
165
|
const applyScrub = (time) => {
|
|
173
|
-
|
|
174
|
-
|
|
166
|
+
const state = playbackRef.current;
|
|
167
|
+
const atEnd = !anyLooping && time >= timelineSpan;
|
|
168
|
+
const shown = atEnd ? timelineSpan : time;
|
|
169
|
+
if (atEnd) {
|
|
170
|
+
scrubFocusToSettled(binding, renderer, timelineSpan);
|
|
171
|
+
markFocusFinished(state, focusRootId);
|
|
172
|
+
pauseFocus(binding);
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
scrubFocusTo(binding, renderer, time);
|
|
176
|
+
clearFocusFinished(state, focusRootId);
|
|
177
|
+
if (stagePlaying) {
|
|
178
|
+
playFocus(binding);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
setFocusElapsed(state, focusRootId, shown);
|
|
175
182
|
if (playheadRef.current) {
|
|
176
|
-
playheadRef.current.style.left = `${LABEL_WIDTH + TIMELINE_INSET +
|
|
183
|
+
playheadRef.current.style.left = `${LABEL_WIDTH + TIMELINE_INSET + shown * pxPerSecond}px`;
|
|
177
184
|
}
|
|
178
185
|
scene.render();
|
|
179
186
|
};
|
|
@@ -188,14 +195,48 @@ export function TimelinePanel(props) {
|
|
|
188
195
|
});
|
|
189
196
|
}
|
|
190
197
|
};
|
|
191
|
-
const
|
|
192
|
-
|
|
198
|
+
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
|
+
}
|
|
206
|
+
if (playheadRef.current) {
|
|
207
|
+
playheadRef.current.style.left = `${LABEL_WIDTH + TIMELINE_INSET}px`;
|
|
208
|
+
}
|
|
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);
|
|
193
226
|
};
|
|
194
227
|
const ticks = [];
|
|
195
228
|
for (let t = 0; t <= timelineSpan; t += pickTickInterval(pxPerSecond)) {
|
|
196
229
|
ticks.push(t);
|
|
197
230
|
}
|
|
198
|
-
const overlayButton = {
|
|
231
|
+
const overlayButton = {
|
|
232
|
+
...buttonStyle,
|
|
233
|
+
padding: '5px 9px',
|
|
234
|
+
minWidth: 32,
|
|
235
|
+
display: 'inline-flex',
|
|
236
|
+
alignItems: 'center',
|
|
237
|
+
justifyContent: 'center',
|
|
238
|
+
gap: 6,
|
|
239
|
+
};
|
|
199
240
|
return (_jsxs("div", { style: {
|
|
200
241
|
flexShrink: 0,
|
|
201
242
|
height: PANEL_HEIGHT,
|
|
@@ -205,71 +246,61 @@ export function TimelinePanel(props) {
|
|
|
205
246
|
flexDirection: 'column',
|
|
206
247
|
fontFamily: theme.font,
|
|
207
248
|
color: theme.text,
|
|
208
|
-
}, 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, title:
|
|
209
|
-
// A finished one-shot effect already snapped itself back to frame 0 and
|
|
210
|
-
// paused (see the tick effect above), so a plain toggle is enough here.
|
|
211
|
-
playbackRef.current.paused = !paused;
|
|
212
|
-
setPaused(!paused);
|
|
213
|
-
}, children: paused ? '▶' : '⏸' }), _jsx("button", { className: "qe-hover", style: overlayButton, title: "Step one frame", disabled: !paused, onClick: () => (playbackRef.current.stepQueued = true), children: "\u23ED" }), _jsx("button", { className: "qe-hover", style: overlayButton, title: "Restart", onClick: restart, children: "\u27F2" }), _jsx("select", { className: "qe-hover", style: { ...overlayButton, padding: '5px 4px' }, title: "Playback speed", value: speed, onChange: (e) => {
|
|
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) => {
|
|
214
250
|
const next = Number(e.target.value);
|
|
215
251
|
playbackRef.current.speed = next;
|
|
216
252
|
setSpeed(next);
|
|
217
|
-
}, 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: ["Group (", groupSelection.size, ")"] }))] }),
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
}, onPointerUp: (e) => {
|
|
235
|
-
playbackRef.current.scrubbing = false;
|
|
236
|
-
e.target.releasePointerCapture?.(e.pointerId);
|
|
237
|
-
}, 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))) })] }), _jsxs("div", { className: "qe-hover-bg", title: "Preview parent \u2014 simulates effect.parent = someNode; not saved in the exported JSON", style: {
|
|
238
|
-
position: 'absolute',
|
|
239
|
-
top: RULER_HEIGHT,
|
|
240
|
-
left: 0,
|
|
241
|
-
right: 0,
|
|
242
|
-
height: STAGE_ROW_HEIGHT,
|
|
243
|
-
display: 'flex',
|
|
244
|
-
alignItems: 'center',
|
|
245
|
-
gap: 6,
|
|
246
|
-
padding: '0 8px',
|
|
247
|
-
fontSize: 12.5,
|
|
248
|
-
fontStyle: 'italic',
|
|
249
|
-
cursor: 'pointer',
|
|
250
|
-
color: selectedNode === null ? theme.text : theme.textDim,
|
|
251
|
-
borderBottom: `1px solid ${theme.border}`,
|
|
252
|
-
...(selectedNode === null ? { background: 'rgba(60, 105, 209, 0.3)' } : {}),
|
|
253
|
-
}, onClick: () => onSelectNode(null), children: [_jsx("span", { style: { fontSize: 10, width: 10 }, children: "\u25CE" }), _jsx("span", { children: "Stage (preview parent)" })] }), _jsx("div", { style: {
|
|
254
|
-
position: 'absolute',
|
|
255
|
-
top: RULER_HEIGHT + STAGE_ROW_HEIGHT,
|
|
256
|
-
left: 0,
|
|
257
|
-
right: 0,
|
|
258
|
-
bottom: 0,
|
|
259
|
-
overflowY: 'auto',
|
|
260
|
-
overflowX: 'hidden',
|
|
261
|
-
}, children: _jsx("div", { style: {
|
|
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: {
|
|
254
|
+
position: 'absolute',
|
|
255
|
+
inset: 0,
|
|
256
|
+
display: 'flex',
|
|
257
|
+
alignItems: 'center',
|
|
258
|
+
justifyContent: 'center',
|
|
259
|
+
padding: '0 24px',
|
|
260
|
+
textAlign: 'center',
|
|
261
|
+
fontSize: 13,
|
|
262
|
+
color: theme.textDim,
|
|
263
|
+
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: {
|
|
265
|
+
position: 'absolute',
|
|
266
|
+
top: 0,
|
|
267
|
+
left: 0,
|
|
268
|
+
right: 0,
|
|
269
|
+
height: RULER_HEIGHT,
|
|
262
270
|
display: 'grid',
|
|
263
271
|
gridTemplateColumns: `${LABEL_WIDTH}px 1fr`,
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
position: '
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
272
|
+
background: theme.panelBg,
|
|
273
|
+
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) => {
|
|
275
|
+
playbackRef.current.scrubbing = true;
|
|
276
|
+
e.target.setPointerCapture(e.pointerId);
|
|
277
|
+
queueScrub(e.clientX);
|
|
278
|
+
}, onPointerMove: (e) => {
|
|
279
|
+
if (playbackRef.current.scrubbing)
|
|
280
|
+
queueScrub(e.clientX);
|
|
281
|
+
}, onPointerUp: (e) => {
|
|
282
|
+
playbackRef.current.scrubbing = false;
|
|
283
|
+
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: {
|
|
285
|
+
position: 'absolute',
|
|
286
|
+
top: RULER_HEIGHT,
|
|
287
|
+
left: 0,
|
|
288
|
+
right: 0,
|
|
289
|
+
bottom: 0,
|
|
290
|
+
overflowY: 'auto',
|
|
291
|
+
overflowX: 'hidden',
|
|
292
|
+
}, children: _jsx("div", { style: {
|
|
293
|
+
display: 'grid',
|
|
294
|
+
gridTemplateColumns: `${LABEL_WIDTH}px 1fr`,
|
|
295
|
+
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: {
|
|
297
|
+
position: 'absolute',
|
|
298
|
+
top: 0,
|
|
299
|
+
bottom: 0,
|
|
300
|
+
left: LABEL_WIDTH + TIMELINE_INSET,
|
|
301
|
+
width: 1,
|
|
302
|
+
background: theme.accent,
|
|
303
|
+
pointerEvents: 'none',
|
|
304
|
+
zIndex: 3,
|
|
305
|
+
} })] })) })] }));
|
|
275
306
|
}
|
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useState } from 'react';
|
|
3
|
+
import { CheckIcon, ChevronDownIcon, ChevronRightIcon, EyeIcon, EyeSlashIcon, PencilSquareIcon, PlusIcon, SparklesIcon, SquaresPlusIcon, XMarkIcon, } from '@heroicons/react/24/solid';
|
|
3
4
|
import { buildEffectTree, collectSystems } from '../core/effectTree';
|
|
4
5
|
import { createChildSystem, removeSystems } from '../core/systems';
|
|
5
6
|
import { ungroupNode } from '../core/groups';
|
|
6
7
|
import { PromptDialog } from './PromptDialog';
|
|
8
|
+
import { iconStyle } from './icons';
|
|
7
9
|
import { theme } from './theme';
|
|
8
|
-
const iconButton = {
|
|
10
|
+
const iconButton = {
|
|
11
|
+
background: 'none',
|
|
12
|
+
border: 'none',
|
|
13
|
+
cursor: 'pointer',
|
|
14
|
+
padding: 0,
|
|
15
|
+
display: 'inline-flex',
|
|
16
|
+
alignItems: 'center',
|
|
17
|
+
justifyContent: 'center',
|
|
18
|
+
};
|
|
9
19
|
const ROW_HEIGHT = 28;
|
|
10
|
-
const checkboxButton = { ...iconButton,
|
|
20
|
+
const checkboxButton = { ...iconButton, width: 12, height: 12 };
|
|
11
21
|
/** One row of the timeline: a label cell (name + actions) and, for tracks, a bar cell. */
|
|
12
22
|
export function TimelineTrackRow(props) {
|
|
13
23
|
const { row, binding, selected, onSelectNode, pxPerSecond, offsetPx, collapsed, onToggleCollapse, hidden, onToggleVisible, groupSelected, onToggleGroupSelect } = props;
|
|
@@ -34,10 +44,10 @@ export function TimelineTrackRow(props) {
|
|
|
34
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) => {
|
|
35
45
|
e.stopPropagation();
|
|
36
46
|
onToggleGroupSelect();
|
|
37
|
-
}, children: groupSelected ? '
|
|
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) => {
|
|
38
48
|
e.stopPropagation();
|
|
39
49
|
onToggleCollapse?.();
|
|
40
|
-
}, children: collapsed ?
|
|
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) => {
|
|
41
51
|
e.stopPropagation();
|
|
42
52
|
let created;
|
|
43
53
|
binding.apply(() => {
|
|
@@ -51,17 +61,17 @@ export function TimelineTrackRow(props) {
|
|
|
51
61
|
binding.addSubSystem(created);
|
|
52
62
|
onSelectNode(created.emitter);
|
|
53
63
|
}
|
|
54
|
-
}, children:
|
|
64
|
+
}, children: _jsx(PlusIcon, { style: iconStyle(13) }) }), !isRoot && (_jsx("button", { className: "qe-hover", title: "Rename", style: { ...iconButton, color: theme.textDim }, onClick: (e) => {
|
|
55
65
|
e.stopPropagation();
|
|
56
66
|
setRenaming(true);
|
|
57
|
-
}, children:
|
|
67
|
+
}, children: _jsx(PencilSquareIcon, { style: iconStyle(12) }) })), !isRoot && (_jsx("button", { className: "qe-hover", title: "Ungroup (moves its contents up a level)", style: { ...iconButton, color: theme.textDim }, onClick: (e) => {
|
|
58
68
|
e.stopPropagation();
|
|
59
69
|
binding.apply(() => ungroupNode(row.node));
|
|
60
|
-
}, children:
|
|
70
|
+
}, children: _jsx(SquaresPlusIcon, { style: iconStyle(12) }) })), !isRoot && (_jsx("button", { className: "qe-hover", title: "Remove group", style: { ...iconButton, color: '#e08c8c' }, onClick: (e) => {
|
|
61
71
|
e.stopPropagation();
|
|
62
72
|
removeSystems(binding, collectSystems(buildEffectTree(row.node)), (s) => onSelectNode(s.emitter));
|
|
63
73
|
binding.apply(() => row.node.dispose(true, false));
|
|
64
|
-
}, children:
|
|
74
|
+
}, children: _jsx(XMarkIcon, { style: iconStyle(12) }) }))] }), _jsx("div", { className: "qe-hover-bg", style: { height: ROW_HEIGHT, borderTop: `1px solid ${theme.border}` }, onClick: () => onSelectNode(row.node) }), renaming && (_jsx(PromptDialog, { title: "Rename", defaultValue: row.name, onSubmit: (name) => {
|
|
65
75
|
binding.apply(() => (row.node.name = name));
|
|
66
76
|
setRenaming(false);
|
|
67
77
|
}, onCancel: () => setRenaming(false) }))] }));
|
|
@@ -72,16 +82,16 @@ export function TimelineTrackRow(props) {
|
|
|
72
82
|
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) => {
|
|
73
83
|
e.stopPropagation();
|
|
74
84
|
onToggleGroupSelect();
|
|
75
|
-
}, children: groupSelected ? '
|
|
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) => {
|
|
76
86
|
e.stopPropagation();
|
|
77
87
|
onToggleVisible?.();
|
|
78
|
-
}, children: hidden ?
|
|
88
|
+
}, 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) => {
|
|
79
89
|
e.stopPropagation();
|
|
80
90
|
setRenaming(true);
|
|
81
|
-
}, children:
|
|
91
|
+
}, children: _jsx(PencilSquareIcon, { style: iconStyle(12) }) })), !isRoot && row.removable && (_jsx("button", { className: "qe-hover", title: "Remove system", style: { ...iconButton, color: '#e08c8c' }, onClick: (e) => {
|
|
82
92
|
e.stopPropagation();
|
|
83
93
|
removeSystems(binding, [system], (s) => onSelectNode(s.emitter));
|
|
84
|
-
}, children:
|
|
94
|
+
}, children: _jsx(XMarkIcon, { style: iconStyle(12) }) })), selected && (_jsx("button", { className: "qe-hover", title: "Add child system", style: { ...iconButton, color: theme.accent }, onClick: (e) => {
|
|
85
95
|
e.stopPropagation();
|
|
86
96
|
let created;
|
|
87
97
|
binding.apply(() => {
|
|
@@ -92,7 +102,7 @@ export function TimelineTrackRow(props) {
|
|
|
92
102
|
binding.addSubSystem(created);
|
|
93
103
|
onSelectNode(created.emitter);
|
|
94
104
|
}
|
|
95
|
-
}, children:
|
|
105
|
+
}, children: _jsx(PlusIcon, { style: iconStyle(13) }) }))] }), _jsx("div", { className: "qe-hover-bg", style: { position: 'relative', height: ROW_HEIGHT }, onClick: () => onSelectNode(row.node), children: _jsx("div", { className: "qe-hover", title: row.startOffsetVariable ? 'Start time varies per spawn' : undefined, style: {
|
|
96
106
|
position: 'absolute',
|
|
97
107
|
left: barLeft,
|
|
98
108
|
width: barWidth,
|
|
@@ -100,10 +110,14 @@ export function TimelineTrackRow(props) {
|
|
|
100
110
|
height: ROW_HEIGHT - 10,
|
|
101
111
|
borderRadius: 4,
|
|
102
112
|
cursor: 'pointer',
|
|
103
|
-
|
|
113
|
+
boxSizing: 'border-box',
|
|
114
|
+
background: system.onlyUsedByOther
|
|
115
|
+
? 'repeating-linear-gradient(135deg, rgba(158,185,255,0.25), rgba(158,185,255,0.25) 4px, rgba(158,185,255,0.4) 4px, rgba(158,185,255,0.4) 8px)'
|
|
116
|
+
: selected
|
|
117
|
+
? theme.accent
|
|
118
|
+
: '#3c5aa0',
|
|
104
119
|
border: system.onlyUsedByOther ? `1px dashed ${theme.accent}` : `1px solid ${theme.border}`,
|
|
105
120
|
opacity: hidden ? 0.3 : system.onlyUsedByOther ? 0.75 : 1,
|
|
106
|
-
boxSizing: 'border-box',
|
|
107
121
|
}, children: row.looping && (_jsx("span", { style: { position: 'absolute', right: 4, top: -1, fontSize: 10, color: theme.text }, children: "\u27F3" })) }) }), renaming && (_jsx(PromptDialog, { title: "Rename", defaultValue: row.name, onSubmit: (name) => {
|
|
108
122
|
binding.apply(() => (row.node.name = name));
|
|
109
123
|
setRenaming(false);
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useLayoutEffect, useRef, useState } from 'react';
|
|
3
|
+
import { createPortal } from 'react-dom';
|
|
4
|
+
const TOOLTIP_MAX_WIDTH = 260;
|
|
5
|
+
const VIEWPORT_PAD = 8;
|
|
6
|
+
/** Positions a fixed tooltip beside the anchor, flipping when near the viewport edge. */
|
|
7
|
+
function placeTooltip(anchor) {
|
|
8
|
+
const centerY = anchor.top + anchor.height / 2;
|
|
9
|
+
const top = Math.min(Math.max(centerY, VIEWPORT_PAD + 24), window.innerHeight - VIEWPORT_PAD - 24);
|
|
10
|
+
const preferRight = anchor.right + 8 + TOOLTIP_MAX_WIDTH <= window.innerWidth - VIEWPORT_PAD;
|
|
11
|
+
if (preferRight) {
|
|
12
|
+
return { left: anchor.right + 8, top, placement: 'right' };
|
|
13
|
+
}
|
|
14
|
+
return { left: anchor.left - 8, top, placement: 'left' };
|
|
15
|
+
}
|
|
16
|
+
/** Themed hover/focus tooltip rendered in a portal so it is not clipped by scroll containers. */
|
|
17
|
+
export function Tooltip(props) {
|
|
18
|
+
const { content, children } = props;
|
|
19
|
+
const anchorRef = useRef(null);
|
|
20
|
+
const [open, setOpen] = useState(false);
|
|
21
|
+
const [layout, setLayout] = useState(null);
|
|
22
|
+
const updateLayout = useCallback(() => {
|
|
23
|
+
const anchor = anchorRef.current?.getBoundingClientRect();
|
|
24
|
+
if (!anchor) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
setLayout(placeTooltip(anchor));
|
|
28
|
+
}, []);
|
|
29
|
+
const show = () => {
|
|
30
|
+
updateLayout();
|
|
31
|
+
setOpen(true);
|
|
32
|
+
};
|
|
33
|
+
const hide = () => setOpen(false);
|
|
34
|
+
useLayoutEffect(() => {
|
|
35
|
+
if (!open) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
updateLayout();
|
|
39
|
+
const onReflow = () => updateLayout();
|
|
40
|
+
window.addEventListener('scroll', onReflow, true);
|
|
41
|
+
window.addEventListener('resize', onReflow);
|
|
42
|
+
return () => {
|
|
43
|
+
window.removeEventListener('scroll', onReflow, true);
|
|
44
|
+
window.removeEventListener('resize', onReflow);
|
|
45
|
+
};
|
|
46
|
+
}, [open, updateLayout]);
|
|
47
|
+
const tooltipStyle = layout?.placement === 'left'
|
|
48
|
+
? {
|
|
49
|
+
position: 'fixed',
|
|
50
|
+
left: layout.left,
|
|
51
|
+
top: layout.top,
|
|
52
|
+
transform: 'translate(-100%, -50%)',
|
|
53
|
+
maxWidth: TOOLTIP_MAX_WIDTH,
|
|
54
|
+
}
|
|
55
|
+
: {
|
|
56
|
+
position: 'fixed',
|
|
57
|
+
left: layout?.left,
|
|
58
|
+
top: layout?.top,
|
|
59
|
+
transform: 'translateY(-50%)',
|
|
60
|
+
maxWidth: TOOLTIP_MAX_WIDTH,
|
|
61
|
+
};
|
|
62
|
+
return (_jsxs(_Fragment, { children: [_jsx("span", { ref: anchorRef, style: { display: 'inline-flex', verticalAlign: 'middle' }, onMouseEnter: show, onMouseLeave: hide, onFocus: show, onBlur: hide, children: children }), open && layout
|
|
63
|
+
? createPortal(_jsx("div", { role: "tooltip", className: `qe-tooltip qe-tooltip--${layout.placement}`, style: tooltipStyle, children: content }), document.body)
|
|
64
|
+
: null] }));
|
|
65
|
+
}
|
|
@@ -6,6 +6,8 @@ import { ScalarGenerator } from '../core/values';
|
|
|
6
6
|
*/
|
|
7
7
|
export declare function ValueField(props: {
|
|
8
8
|
label: string;
|
|
9
|
+
hint?: string;
|
|
10
|
+
hintKey?: string;
|
|
9
11
|
generator: ScalarGenerator | undefined;
|
|
10
12
|
onChange: (generator: ScalarGenerator) => void;
|
|
11
13
|
min?: number;
|
package/dist/react/ValueField.js
CHANGED
|
@@ -11,7 +11,7 @@ export function ValueField(props) {
|
|
|
11
11
|
const commit = (patch) => {
|
|
12
12
|
props.onChange(buildScalar({ ...state, ...patch }));
|
|
13
13
|
};
|
|
14
|
-
return (_jsxs(_Fragment, { children: [_jsx(Row, { label: props.label, children: _jsx(SelectField, { value: state.mode, options: [
|
|
14
|
+
return (_jsxs(_Fragment, { children: [_jsx(Row, { label: props.label, hint: props.hint, hintKey: props.hintKey, children: _jsx(SelectField, { value: state.mode, options: [
|
|
15
15
|
{ value: 'constant', label: 'Constant' },
|
|
16
16
|
{ value: 'random', label: 'Random between two' },
|
|
17
17
|
{ value: 'curve', label: 'Curve' },
|
|
@@ -32,7 +32,7 @@ export function LimitSpeedOverLifeModule({ binding }) {
|
|
|
32
32
|
return (_jsx(BehaviorModule, { binding: binding, title: "Limit Speed over Lifetime", type: "LimitSpeedOverLife", create: () => new LimitSpeedOverLife(buildScalar({ mode: 'curve', value: 1, min: 0, max: 1, curve: [4, 2, 1, 1] }), 0.5), children: (behavior) => (_jsxs(_Fragment, { children: [_jsx("div", { style: { marginTop: 6 }, children: _jsx(CurveEditor, { pieces: readPieces(behavior.speed), maxValue: 8, onChange: (pieces) => binding.apply(() => (behavior.speed = buildCurve(pieces))) }) }), _jsx(Row, { label: "Dampen", children: _jsx(NumberField, { value: behavior.dampen, min: 0, step: 0.05, onChange: (v) => binding.apply(() => (behavior.dampen = Math.min(1, v))) }) })] })) }));
|
|
33
33
|
}
|
|
34
34
|
export function ForceOverLifeModule({ binding }) {
|
|
35
|
-
return (_jsx(BehaviorModule, { binding: binding, title: "Force over Lifetime", type: "ForceOverLife", create: () => new ForceOverLife(new ConstantValue(0), new ConstantValue(2), new ConstantValue(0)), children: (behavior) => (_jsxs(_Fragment, { children: [_jsx(ValueField, { label: "X", generator: behavior.x, curveMax: 10, onChange: (g) => binding.apply(() => (behavior.x = g)) }), _jsx(ValueField, { label: "Y", generator: behavior.y, curveMax: 10, onChange: (g) => binding.apply(() => (behavior.y = g)) }), _jsx(ValueField, { label: "Z", generator: behavior.z, curveMax: 10, onChange: (g) => binding.apply(() => (behavior.z = g)) })] })) }));
|
|
35
|
+
return (_jsx(BehaviorModule, { binding: binding, title: "Force over Lifetime", type: "ForceOverLife", create: () => new ForceOverLife(new ConstantValue(0), new ConstantValue(2), new ConstantValue(0)), children: (behavior) => (_jsxs(_Fragment, { children: [_jsx(ValueField, { hintKey: "force.x", label: "X", generator: behavior.x, curveMax: 10, onChange: (g) => binding.apply(() => (behavior.x = g)) }), _jsx(ValueField, { hintKey: "force.y", label: "Y", generator: behavior.y, curveMax: 10, onChange: (g) => binding.apply(() => (behavior.y = g)) }), _jsx(ValueField, { hintKey: "force.z", label: "Z", generator: behavior.z, curveMax: 10, onChange: (g) => binding.apply(() => (behavior.z = g)) })] })) }));
|
|
36
36
|
}
|
|
37
37
|
export function VelocityOverLifeModule({ binding }) {
|
|
38
38
|
return (_jsx(BehaviorModule, { binding: binding, title: "Velocity over Lifetime", type: "VelocityOverLife", create: () => new VelocityOverLife(new ConstantValue(0), new ConstantValue(1), new ConstantValue(0)), children: (behavior) => (_jsxs(_Fragment, { children: [_jsx(Row, { label: "Space", children: _jsx(SelectField, { value: behavior.space, options: [
|
|
@@ -41,19 +41,19 @@ export function VelocityOverLifeModule({ binding }) {
|
|
|
41
41
|
], onChange: (v) => binding.apply(() => (behavior.space = v)) }) }), _jsx(ValueField, { label: "Linear X", generator: behavior.linearX, curveMax: 10, onChange: (g) => binding.apply(() => (behavior.linearX = g)) }), _jsx(ValueField, { label: "Linear Y", generator: behavior.linearY, curveMax: 10, onChange: (g) => binding.apply(() => (behavior.linearY = g)) }), _jsx(ValueField, { label: "Linear Z", generator: behavior.linearZ, curveMax: 10, onChange: (g) => binding.apply(() => (behavior.linearZ = g)) }), _jsx(ValueField, { label: "Orbital X", generator: behavior.orbitalX, curveMax: Math.PI * 4, onChange: (g) => binding.apply(() => (behavior.orbitalX = g)) }), _jsx(ValueField, { label: "Orbital Y", generator: behavior.orbitalY, curveMax: Math.PI * 4, onChange: (g) => binding.apply(() => (behavior.orbitalY = g)) }), _jsx(ValueField, { label: "Orbital Z", generator: behavior.orbitalZ, curveMax: Math.PI * 4, onChange: (g) => binding.apply(() => (behavior.orbitalZ = g)) })] })) }));
|
|
42
42
|
}
|
|
43
43
|
export function InheritVelocityModule({ binding }) {
|
|
44
|
-
return (_jsx(BehaviorModule, { binding: binding, title: "Inherit Velocity", type: "InheritVelocity", create: () => new InheritVelocity(new ConstantValue(1), 'initial'), children: (behavior) => (_jsxs(_Fragment, { children: [_jsx(Row, { label: "Mode", children: _jsx(SelectField, { value: behavior.mode, options: [
|
|
44
|
+
return (_jsx(BehaviorModule, { binding: binding, title: "Inherit Velocity", type: "InheritVelocity", create: () => new InheritVelocity(new ConstantValue(1), 'initial'), children: (behavior) => (_jsxs(_Fragment, { children: [_jsx(Row, { label: "Mode", hintKey: "inheritVelocity.mode", children: _jsx(SelectField, { value: behavior.mode, options: [
|
|
45
45
|
{ value: 'initial', label: 'Initial' },
|
|
46
46
|
{ value: 'current', label: 'Current' },
|
|
47
47
|
], onChange: (v) => binding.apply(() => (behavior.mode = v)) }) }), _jsx(ValueField, { label: "Multiplier", generator: behavior.multiplier, curveMax: 2, onChange: (g) => binding.apply(() => (behavior.multiplier = g)) })] })) }));
|
|
48
48
|
}
|
|
49
49
|
export function GravityModule({ binding }) {
|
|
50
|
-
return (_jsx(BehaviorModule, { binding: binding, title: "Gravity", type: "ApplyForce", create: () => new ApplyForce(new Vector3(0, -1, 0), new ConstantValue(9.81)), children: (behavior) => (_jsx(Row, { label: "Strength", children: _jsx(NumberField, { value: readScalar(behavior.magnitude).value, step: 0.5, onChange: (v) => binding.apply(() => (behavior.magnitude = new ConstantValue(v))) }) })) }));
|
|
50
|
+
return (_jsx(BehaviorModule, { binding: binding, title: "Gravity", type: "ApplyForce", create: () => new ApplyForce(new Vector3(0, -1, 0), new ConstantValue(9.81)), children: (behavior) => (_jsx(Row, { label: "Strength", hintKey: "gravity.strength", children: _jsx(NumberField, { value: readScalar(behavior.magnitude).value, step: 0.5, onChange: (v) => binding.apply(() => (behavior.magnitude = new ConstantValue(v))) }) })) }));
|
|
51
51
|
}
|
|
52
52
|
export function RotationOverLifeModule({ binding }) {
|
|
53
|
-
return (_jsx(BehaviorModule, { binding: binding, title: "Rotation over Lifetime", type: "RotationOverLife", create: () => new RotationOverLife(new ConstantValue(Math.PI)), children: (behavior) => (_jsx(ValueField, { label: "Velocity (rad/s)", generator: behavior.angularVelocity, curveMax: Math.PI * 4, onChange: (g) => binding.apply(() => (behavior.angularVelocity = g)) })) }));
|
|
53
|
+
return (_jsx(BehaviorModule, { binding: binding, title: "Rotation over Lifetime", type: "RotationOverLife", create: () => new RotationOverLife(new ConstantValue(Math.PI)), children: (behavior) => (_jsx(ValueField, { hintKey: "rotationOverLife.velocity", label: "Velocity (rad/s)", generator: behavior.angularVelocity, curveMax: Math.PI * 4, onChange: (g) => binding.apply(() => (behavior.angularVelocity = g)) })) }));
|
|
54
54
|
}
|
|
55
55
|
export function NoiseModule({ binding }) {
|
|
56
|
-
return (_jsx(BehaviorModule, { binding: binding, title: "Noise", type: "Noise", create: () => new Noise(new ConstantValue(1), new ConstantValue(1), new ConstantValue(1), new ConstantValue(0)), children: (behavior) => (_jsxs(_Fragment, { children: [_jsx(ValueField, { label: "Frequency", generator: behavior.frequency, min: 0, curveMax: 10, onChange: (g) => binding.apply(() => (behavior.frequency = g)) }), _jsx(ValueField, { label: "Strength", generator: behavior.power, min: 0, curveMax: 10, onChange: (g) => binding.apply(() => (behavior.power = g)) }), _jsx(ValueField, { label: "Position amount", generator: behavior.positionAmount, min: 0, curveMax: 2, onChange: (g) => binding.apply(() => (behavior.positionAmount = g)) }), _jsx(ValueField, { label: "Rotation amount", generator: behavior.rotationAmount, min: 0, curveMax: Math.PI, onChange: (g) => binding.apply(() => (behavior.rotationAmount = g)) })] })) }));
|
|
56
|
+
return (_jsx(BehaviorModule, { binding: binding, title: "Noise", type: "Noise", create: () => new Noise(new ConstantValue(1), new ConstantValue(1), new ConstantValue(1), new ConstantValue(0)), children: (behavior) => (_jsxs(_Fragment, { children: [_jsx(ValueField, { hintKey: "noise.frequency", label: "Frequency", generator: behavior.frequency, min: 0, curveMax: 10, onChange: (g) => binding.apply(() => (behavior.frequency = g)) }), _jsx(ValueField, { hintKey: "noise.strength", label: "Strength", generator: behavior.power, min: 0, curveMax: 10, onChange: (g) => binding.apply(() => (behavior.power = g)) }), _jsx(ValueField, { label: "Position amount", generator: behavior.positionAmount, min: 0, curveMax: 2, onChange: (g) => binding.apply(() => (behavior.positionAmount = g)) }), _jsx(ValueField, { label: "Rotation amount", generator: behavior.rotationAmount, min: 0, curveMax: Math.PI, onChange: (g) => binding.apply(() => (behavior.rotationAmount = g)) })] })) }));
|
|
57
57
|
}
|
|
58
58
|
/** Three numeric inputs for a Vector3 field, replacing the whole vector on any edit. */
|
|
59
59
|
function Vector3Row(props) {
|
|
@@ -62,10 +62,10 @@ function Vector3Row(props) {
|
|
|
62
62
|
next[axis] = n;
|
|
63
63
|
props.onChange(next);
|
|
64
64
|
};
|
|
65
|
-
return (_jsx(Row, { label: props.label, children: _jsxs("div", { style: { display: 'flex', gap: 6 }, children: [_jsx(NumberField, { value: props.value.x, min: props.min, step: props.step, onChange: (n) => set('x', n) }), _jsx(NumberField, { value: props.value.y, min: props.min, step: props.step, onChange: (n) => set('y', n) }), _jsx(NumberField, { value: props.value.z, min: props.min, step: props.step, onChange: (n) => set('z', n) })] }) }));
|
|
65
|
+
return (_jsx(Row, { label: props.label, hintKey: props.hintKey, children: _jsxs("div", { style: { display: 'flex', gap: 6 }, children: [_jsx(NumberField, { value: props.value.x, min: props.min, step: props.step, onChange: (n) => set('x', n) }), _jsx(NumberField, { value: props.value.y, min: props.min, step: props.step, onChange: (n) => set('y', n) }), _jsx(NumberField, { value: props.value.z, min: props.min, step: props.step, onChange: (n) => set('z', n) })] }) }));
|
|
66
66
|
}
|
|
67
67
|
export function TurbulenceModule({ binding }) {
|
|
68
|
-
return (_jsx(BehaviorModule, { binding: binding, title: "Turbulence", type: "TurbulenceField", create: () => new TurbulenceField(new Vector3(1, 1, 1), 3, new Vector3(1, 1, 1), new Vector3(1, 1, 1)), children: (behavior) => (_jsxs(_Fragment, { children: [_jsx(Vector3Row, { label: "Scale", value: behavior.scale, min: 0.01, step: 0.1, onChange: (v) => binding.apply(() => (behavior.scale = v)) }), _jsx(Row, { label: "Octaves", children: _jsx(NumberField, { value: behavior.octaves, min: 1, step: 1, onChange: (v) => binding.apply(() => (behavior.octaves = Math.max(1, Math.round(v)))) }) }), _jsx(Vector3Row, { label: "Strength", value: behavior.velocityMultiplier, step: 0.5, onChange: (v) => binding.apply(() => (behavior.velocityMultiplier = v)) }), _jsx(Vector3Row, { label: "Time scale", value: behavior.timeScale, step: 0.1, onChange: (v) => binding.apply(() => (behavior.timeScale = v)) })] })) }));
|
|
68
|
+
return (_jsx(BehaviorModule, { binding: binding, title: "Turbulence", type: "TurbulenceField", create: () => new TurbulenceField(new Vector3(1, 1, 1), 3, new Vector3(1, 1, 1), new Vector3(1, 1, 1)), children: (behavior) => (_jsxs(_Fragment, { children: [_jsx(Vector3Row, { hintKey: "turbulence.scale", label: "Scale", value: behavior.scale, min: 0.01, step: 0.1, onChange: (v) => binding.apply(() => (behavior.scale = v)) }), _jsx(Row, { label: "Octaves", children: _jsx(NumberField, { value: behavior.octaves, min: 1, step: 1, onChange: (v) => binding.apply(() => (behavior.octaves = Math.max(1, Math.round(v)))) }) }), _jsx(Vector3Row, { hintKey: "turbulence.strength", label: "Strength", value: behavior.velocityMultiplier, step: 0.5, onChange: (v) => binding.apply(() => (behavior.velocityMultiplier = v)) }), _jsx(Vector3Row, { label: "Time scale", value: behavior.timeScale, step: 0.1, onChange: (v) => binding.apply(() => (behavior.timeScale = v)) })] })) }));
|
|
69
69
|
}
|
|
70
70
|
export function EmitDirectionModule({ binding }) {
|
|
71
71
|
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)) })) }));
|
|
@@ -87,7 +87,7 @@ 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 })] })) }));
|
|
88
88
|
}
|
|
89
89
|
export function RotationBySpeedModule({ binding }) {
|
|
90
|
-
return (_jsx(BehaviorModule, { binding: binding, title: "Rotation by Speed", type: "RotationBySpeed", create: () => new RotationBySpeed(new ConstantValue(Math.PI), new IntervalValue(0, 5)), children: (behavior) => (_jsxs(_Fragment, { children: [_jsx(ValueField, { label: "Velocity (rad/s)", generator: behavior.angularVelocity, curveMax: Math.PI * 4, onChange: (g) => binding.apply(() => (behavior.angularVelocity = g)) }), _jsx(SpeedRangeRows, { binding: binding, behavior: behavior })] })) }));
|
|
90
|
+
return (_jsx(BehaviorModule, { binding: binding, title: "Rotation by Speed", type: "RotationBySpeed", create: () => new RotationBySpeed(new ConstantValue(Math.PI), new IntervalValue(0, 5)), children: (behavior) => (_jsxs(_Fragment, { children: [_jsx(ValueField, { hintKey: "rotationBySpeed.velocity", label: "Velocity (rad/s)", generator: behavior.angularVelocity, curveMax: Math.PI * 4, onChange: (g) => binding.apply(() => (behavior.angularVelocity = g)) }), _jsx(SpeedRangeRows, { binding: binding, behavior: behavior })] })) }));
|
|
91
91
|
}
|
|
92
92
|
export function OrbitOverLifeModule({ binding }) {
|
|
93
93
|
return (_jsx(BehaviorModule, { binding: binding, title: "Orbit over Lifetime", type: "OrbitOverLife", create: () => new OrbitOverLife(new ConstantValue(Math.PI), new Vector3(0, 1, 0)), children: (behavior) => (_jsx(ValueField, { label: "Orbit speed", generator: behavior.orbitSpeed, curveMax: Math.PI * 4, onChange: (g) => binding.apply(() => (behavior.orbitSpeed = g)) })) }));
|
package/dist/react/fields.d.ts
CHANGED