babylon.quarks-editor 0.18.0 → 0.19.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/README.md +6 -6
- package/dist/core/binding.d.ts +1 -1
- package/dist/core/colors.d.ts +1 -1
- package/dist/core/defaultEffect.d.ts +1 -1
- package/dist/core/effectTree.d.ts +1 -1
- package/dist/core/effectTree.js +18 -4
- package/dist/core/emitterShapeWireframe.d.ts +1 -1
- package/dist/core/emitterShapeWireframe.js +1 -1
- package/dist/core/galleryDropMarker.js +1 -1
- package/dist/core/galleryScene.d.ts +1 -1
- package/dist/core/galleryTree.js +4 -1
- package/dist/core/loadEffect.d.ts +2 -2
- package/dist/core/loadEffect.js +1 -1
- package/dist/core/loadEffectGallery.d.ts +2 -2
- package/dist/core/shapes.d.ts +1 -1
- package/dist/core/shapes.js +35 -8
- package/dist/core/systems.d.ts +1 -1
- package/dist/core/systems.js +3 -1
- package/dist/core/timeline.d.ts +1 -2
- package/dist/core/values.d.ts +1 -1
- package/dist/index.d.ts +11 -11
- package/dist/index.js +6 -6
- package/dist/react/CurveEditor.d.ts +1 -2
- package/dist/react/CurveEditor.js +14 -3
- package/dist/react/EffectEditor.d.ts +2 -2
- package/dist/react/EffectEditor.js +2 -2
- package/dist/react/EffectEditorHost.d.ts +1 -1
- package/dist/react/EffectEditorHost.js +123 -33
- package/dist/react/GalleryPanel.d.ts +1 -1
- package/dist/react/GalleryPanel.js +14 -7
- package/dist/react/GradientEditor.d.ts +1 -2
- package/dist/react/GradientEditor.js +8 -1
- package/dist/react/MessageDialog.js +7 -1
- package/dist/react/ModuleSection.js +10 -3
- package/dist/react/ObjectModule.d.ts +1 -2
- package/dist/react/ObjectModule.js +1 -1
- package/dist/react/SubEmittersModule.d.ts +1 -2
- package/dist/react/TimelinePanel.d.ts +3 -3
- package/dist/react/TimelinePanel.js +55 -9
- package/dist/react/TimelineTrackRow.d.ts +1 -1
- package/dist/react/TimelineTrackRow.js +20 -6
- package/dist/react/ValueField.d.ts +1 -2
- package/dist/react/behaviorModules.js +19 -5
- package/dist/react/index.d.ts +17 -17
- package/dist/react/index.js +11 -11
- package/dist/react/modules.js +55 -25
- package/package.json +68 -68
- package/src/core/binding.ts +2 -2
- package/src/core/colors.ts +1 -1
- package/src/core/defaultEffect.ts +1 -1
- package/src/core/effectTree.ts +22 -7
- package/src/core/emitterShapeWireframe.ts +8 -4
- package/src/core/galleryDropMarker.ts +6 -2
- package/src/core/galleryPlayback.ts +1 -1
- package/src/core/galleryScene.ts +3 -7
- package/src/core/gallerySelectionMarker.ts +5 -1
- package/src/core/galleryTree.ts +8 -5
- package/src/core/loadEffect.ts +3 -3
- package/src/core/loadEffectGallery.ts +7 -3
- package/src/core/pickGalleryFiles.ts +4 -1
- package/src/core/shapes.ts +48 -10
- package/src/core/systems.ts +9 -3
- package/src/core/timeline.ts +8 -8
- package/src/core/values.ts +1 -1
- package/src/core/viewportPicker.ts +1 -1
- package/src/index.ts +18 -18
- package/src/react/CurveEditor.tsx +40 -10
- package/src/react/EffectEditor.tsx +14 -13
- package/src/react/EffectEditorHost.tsx +239 -64
- package/src/react/GalleryPanel.tsx +56 -12
- package/src/react/GradientEditor.tsx +21 -4
- package/src/react/InspectorInfoIcon.tsx +6 -2
- package/src/react/MessageDialog.tsx +9 -1
- package/src/react/ModuleSection.tsx +12 -3
- package/src/react/ObjectModule.tsx +36 -15
- package/src/react/PromptDialog.tsx +5 -1
- package/src/react/SubEmittersModule.tsx +1 -2
- package/src/react/TimelinePanel.tsx +94 -21
- package/src/react/TimelineTrackRow.tsx +71 -18
- package/src/react/ValueField.tsx +0 -1
- package/src/react/behaviorModules.tsx +143 -29
- package/src/react/index.ts +33 -33
- package/src/react/modules.tsx +173 -57
|
@@ -1,48 +1,48 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useMemo, useReducer, useRef, useState, useCallback } from 'react';
|
|
3
|
-
import { createRoot } from 'react-dom/client';
|
|
4
|
-
import { ArrowLeftIcon, ArrowPathIcon, ArrowUturnLeftIcon, ArrowUturnRightIcon, ArrowsPointingOutIcon, CubeTransparentIcon, CursorArrowRaysIcon, Square2StackIcon, } from '@heroicons/react/24/solid';
|
|
5
|
-
import { PointerEventTypes } from '@babylonjs/core/Events/pointerEvents';
|
|
6
|
-
import { Engine } from '@babylonjs/core/Engines/engine';
|
|
7
|
-
import { Scene } from '@babylonjs/core/scene';
|
|
8
2
|
import { ArcRotateCamera } from '@babylonjs/core/Cameras/arcRotateCamera';
|
|
3
|
+
import { Engine } from '@babylonjs/core/Engines/engine';
|
|
4
|
+
import { PointerEventTypes } from '@babylonjs/core/Events/pointerEvents';
|
|
5
|
+
import { GizmoManager } from '@babylonjs/core/Gizmos/gizmoManager';
|
|
9
6
|
import { DirectionalLight } from '@babylonjs/core/Lights/directionalLight';
|
|
10
|
-
import { Vector3 as BVector3 } from '@babylonjs/core/Maths/math.vector';
|
|
11
7
|
import { Color3, Color4 } from '@babylonjs/core/Maths/math.color';
|
|
8
|
+
import { Vector3 as BVector3 } from '@babylonjs/core/Maths/math.vector';
|
|
12
9
|
import { MeshBuilder } from '@babylonjs/core/Meshes/meshBuilder';
|
|
13
10
|
import { TransformNode } from '@babylonjs/core/Meshes/transformNode';
|
|
14
|
-
import { GizmoManager } from '@babylonjs/core/Gizmos/gizmoManager';
|
|
15
11
|
import { UtilityLayerRenderer } from '@babylonjs/core/Rendering/utilityLayerRenderer';
|
|
12
|
+
import { Scene } from '@babylonjs/core/scene';
|
|
16
13
|
import { GridMaterial } from '@babylonjs/materials/grid/gridMaterial';
|
|
17
|
-
import {
|
|
14
|
+
import { ArrowLeftIcon, ArrowPathIcon, ArrowsPointingOutIcon, ArrowUturnLeftIcon, ArrowUturnRightIcon, CubeTransparentIcon, CursorArrowRaysIcon, Square2StackIcon, } from '@heroicons/react/24/solid';
|
|
15
|
+
import { BatchedRenderer, QuarksUtil } from 'babylon.quarks';
|
|
16
|
+
import { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react';
|
|
17
|
+
import { createRoot } from 'react-dom/client';
|
|
18
18
|
import { EffectBinding } from '../core/binding';
|
|
19
|
-
import { EffectHistory } from '../core/history';
|
|
20
19
|
import { ensureGroundResolver } from '../core/collision';
|
|
21
20
|
import { createGalleryDefaultEntry } from '../core/defaultEffect';
|
|
22
|
-
import { loadEffectFromJson, disposeLoadedEffect, parseEffectFromJson } from '../core/loadEffect';
|
|
23
|
-
import { applyGalleryPlayback, countActivePreviews, countGalleryParticles, hasActivePreview, pauseFinishedPreviews } from '../core/galleryPlayback';
|
|
24
|
-
import { createPlaybackState, clearFocusFinished, getFocusElapsed, isFocusFinished, markFocusFinished, setFocusElapsed } from '../core/playback';
|
|
25
|
-
import { pauseFocus, scrubFocusToSettled } from '../core/scrub';
|
|
26
|
-
import { logRenderMismatch } from '../core/renderDebug';
|
|
27
|
-
import { cancelGalleryCameraFocus, focusGalleryEntry } from '../core/galleryLayout';
|
|
28
|
-
import { computeTimelineRows, computeTimelineSpan } from '../core/timeline';
|
|
29
|
-
import { GalleryDropMarker } from '../core/galleryDropMarker';
|
|
30
|
-
import { GallerySelectionMarker } from '../core/gallerySelectionMarker';
|
|
31
21
|
import { EmitterShapeWireframes } from '../core/emitterShapeWireframe';
|
|
32
22
|
import { getGalleryDragSession, setGalleryDragSession } from '../core/galleryDragSession';
|
|
23
|
+
import { GalleryDropMarker } from '../core/galleryDropMarker';
|
|
24
|
+
import { cancelGalleryCameraFocus, focusGalleryEntry } from '../core/galleryLayout';
|
|
25
|
+
import { applyGalleryPlayback, countActivePreviews, countGalleryParticles, hasActivePreview, pauseFinishedPreviews, } from '../core/galleryPlayback';
|
|
33
26
|
import { clearGalleryEntrySimulation, pickGroundPosition, placeGalleryEntriesAt, placeGalleryEntryInScene, removeGalleryEntryFromScene, } from '../core/galleryScene';
|
|
27
|
+
import { GallerySelectionMarker } from '../core/gallerySelectionMarker';
|
|
34
28
|
import { describeGalleryDrag, GALLERY_DRAG_MIME, readGalleryDrag } from '../core/galleryTree';
|
|
35
|
-
import {
|
|
29
|
+
import { EffectHistory } from '../core/history';
|
|
30
|
+
import { disposeLoadedEffect, loadEffectFromJson, parseEffectFromJson } from '../core/loadEffect';
|
|
31
|
+
import { bindingFromGalleryEntry, collectJsonFiles, loadEffectGallery, mergeGalleryEntries, } from '../core/loadEffectGallery';
|
|
36
32
|
import { pickGalleryJsonFiles } from '../core/pickGalleryFiles';
|
|
37
|
-
import {
|
|
33
|
+
import { clearFocusFinished, createPlaybackState, getFocusElapsed, isFocusFinished, markFocusFinished, setFocusElapsed, } from '../core/playback';
|
|
34
|
+
import { logRenderMismatch } from '../core/renderDebug';
|
|
35
|
+
import { pauseFocus, scrubFocusToSettled } from '../core/scrub';
|
|
36
|
+
import { computeTimelineRows, computeTimelineSpan } from '../core/timeline';
|
|
38
37
|
import { notifyTransformChanged } from '../core/transformStore';
|
|
38
|
+
import { GalleryViewportPicker } from '../core/viewportPicker';
|
|
39
39
|
import { EffectEditor } from './EffectEditor';
|
|
40
|
-
import { PromptDialog } from './PromptDialog';
|
|
41
|
-
import { MessageDialog } from './MessageDialog';
|
|
42
40
|
import { GalleryPanel } from './GalleryPanel';
|
|
41
|
+
import { MessageDialog } from './MessageDialog';
|
|
42
|
+
import { PromptDialog } from './PromptDialog';
|
|
43
43
|
import { TimelinePanel } from './TimelinePanel';
|
|
44
|
-
import { buttonStyle, globalEditorStyles, theme } from './theme';
|
|
45
44
|
import { iconStyle } from './icons';
|
|
45
|
+
import { buttonStyle, globalEditorStyles, theme } from './theme';
|
|
46
46
|
/** Disposes an effect's systems and its remaining group tree (otherwise the groups leak). */
|
|
47
47
|
function disposeEffect(root, systems, renderer, inRenderer = true) {
|
|
48
48
|
disposeLoadedEffect(root, systems, renderer, inRenderer);
|
|
@@ -548,7 +548,9 @@ export function EffectEditorHost(props) {
|
|
|
548
548
|
}
|
|
549
549
|
const previewIds = new Set(snapshot.previewRootIds);
|
|
550
550
|
const binding = snapshot.binding;
|
|
551
|
-
if (binding &&
|
|
551
|
+
if (binding &&
|
|
552
|
+
(binding.root.uniqueId !== focusTimelineMetaRef.current.rootId ||
|
|
553
|
+
binding.getRevision() !== focusTimelineMetaRef.current.revision)) {
|
|
552
554
|
const rows = computeTimelineRows(binding);
|
|
553
555
|
focusTimelineMetaRef.current = {
|
|
554
556
|
rootId: binding.root.uniqueId,
|
|
@@ -660,7 +662,13 @@ export function EffectEditorHost(props) {
|
|
|
660
662
|
gm.positionGizmoEnabled = gizmoMode === 'position';
|
|
661
663
|
gm.rotationGizmoEnabled = gizmoMode === 'rotation';
|
|
662
664
|
gm.scaleGizmoEnabled = gizmoMode === 'scale';
|
|
663
|
-
const activeGizmo = gizmoMode === 'position'
|
|
665
|
+
const activeGizmo = gizmoMode === 'position'
|
|
666
|
+
? gm.gizmos.positionGizmo
|
|
667
|
+
: gizmoMode === 'rotation'
|
|
668
|
+
? gm.gizmos.rotationGizmo
|
|
669
|
+
: gizmoMode === 'scale'
|
|
670
|
+
? gm.gizmos.scaleGizmo
|
|
671
|
+
: null;
|
|
664
672
|
let dragRaf = 0;
|
|
665
673
|
const scheduleTransformRefresh = () => {
|
|
666
674
|
if (dragRaf !== 0) {
|
|
@@ -798,7 +806,7 @@ export function EffectEditorHost(props) {
|
|
|
798
806
|
const activeSystem = binding ? (allSystems.find((s) => s.emitter === selectedNode) ?? binding.system) : null;
|
|
799
807
|
const gizmoTargetLabel = gizmoNode?.name || 'Node';
|
|
800
808
|
const selectedGalleryEntry = selectedGalleryRoot
|
|
801
|
-
? gallery.find((entry) => entry.root === selectedGalleryRoot) ?? null
|
|
809
|
+
? (gallery.find((entry) => entry.root === selectedGalleryRoot) ?? null)
|
|
802
810
|
: null;
|
|
803
811
|
/** Timeline is always shown when an effect is selected; playback only when it is on stage. */
|
|
804
812
|
const showTimeline = Boolean(binding && activeSystem && state);
|
|
@@ -815,7 +823,15 @@ export function EffectEditorHost(props) {
|
|
|
815
823
|
}
|
|
816
824
|
return props.resolveGeometry(file, scene);
|
|
817
825
|
}, [props.resolveGeometry]);
|
|
818
|
-
return (_jsxs("div", { className: "qe-root", style: {
|
|
826
|
+
return (_jsxs("div", { className: "qe-root", style: {
|
|
827
|
+
display: 'flex',
|
|
828
|
+
flexDirection: 'column',
|
|
829
|
+
width: '100%',
|
|
830
|
+
height: '100%',
|
|
831
|
+
fontFamily: theme.font,
|
|
832
|
+
color: theme.text,
|
|
833
|
+
background: '#070b16',
|
|
834
|
+
}, children: [_jsx("style", { children: globalEditorStyles }), _jsxs("div", { style: { display: 'flex', flex: 1, minHeight: 0 }, children: [_jsxs("aside", { style: {
|
|
819
835
|
width: GALLERY_PANEL_WIDTH,
|
|
820
836
|
flexShrink: 0,
|
|
821
837
|
borderRight: `1px solid ${theme.border}`,
|
|
@@ -823,7 +839,14 @@ export function EffectEditorHost(props) {
|
|
|
823
839
|
display: 'flex',
|
|
824
840
|
flexDirection: 'column',
|
|
825
841
|
minHeight: 0,
|
|
826
|
-
}, children: [_jsxs("div", { style: { padding: '12px 14px 10px', borderBottom: `1px solid ${theme.border}` }, children: [_jsxs("div", { style: { fontSize: 14, fontWeight: 600 }, children: ["Gallery", gallery.length > 0 ? ` · ${gallery.length}` : ''] }), _jsx("div", { style: { fontSize: 11, color: theme.textDim, marginTop: 4, lineHeight: 1.4 }, children: "Drag into viewport to place \u00B7 \u2713 preview \u00B7 \u00D7 remove from stage" })] }), _jsx("div", { style: {
|
|
842
|
+
}, children: [_jsxs("div", { style: { padding: '12px 14px 10px', borderBottom: `1px solid ${theme.border}` }, children: [_jsxs("div", { style: { fontSize: 14, fontWeight: 600 }, children: ["Gallery", gallery.length > 0 ? ` · ${gallery.length}` : ''] }), _jsx("div", { style: { fontSize: 11, color: theme.textDim, marginTop: 4, lineHeight: 1.4 }, children: "Drag into viewport to place \u00B7 \u2713 preview \u00B7 \u00D7 remove from stage" })] }), _jsx("div", { style: {
|
|
843
|
+
flex: 1,
|
|
844
|
+
minHeight: 0,
|
|
845
|
+
overflow: 'hidden',
|
|
846
|
+
padding: '8px 12px 14px',
|
|
847
|
+
display: 'flex',
|
|
848
|
+
flexDirection: 'column',
|
|
849
|
+
}, children: _jsx(GalleryPanel, { entries: gallery, focusedRoot: selectedGalleryRoot, previewRootIds: previewRootIdSet, loading: galleryLoading, folderInputRef: galleryFolderInputRef, onFocus: (entry) => selectGalleryEntryRef.current(entry), onTogglePreview: (entry) => toggleGalleryPreviewRef.current(entry), onRemoveFromScene: (entry) => removeGalleryFromSceneRef.current(entry), onImportFiles: (files) => void runGalleryImport(files), onImportFolder: () => void handleImportFolder(), onCreateDefault: () => createGalleryDefaultRef.current() }) })] }), _jsxs("div", { style: { position: 'relative', flex: 1, minWidth: 0 }, onDragEnter: (e) => {
|
|
827
850
|
if (!e.dataTransfer.types.includes(GALLERY_DRAG_MIME))
|
|
828
851
|
return;
|
|
829
852
|
galleryDragDepthRef.current += 1;
|
|
@@ -889,7 +912,7 @@ export function EffectEditorHost(props) {
|
|
|
889
912
|
fontSize: 13,
|
|
890
913
|
color: theme.text,
|
|
891
914
|
whiteSpace: 'nowrap',
|
|
892
|
-
}, children: ["Drop ", galleryDragHover.count === 1 ? 'effect' : `${galleryDragHover.count} effects`, " \u2014
|
|
915
|
+
}, children: ["Drop ", galleryDragHover.count === 1 ? 'effect' : `${galleryDragHover.count} effects`, " \u2014", ' ', galleryDragHover.label] }) })), galleryLoading && (_jsx("div", { style: {
|
|
893
916
|
position: 'absolute',
|
|
894
917
|
inset: 0,
|
|
895
918
|
display: 'flex',
|
|
@@ -897,7 +920,13 @@ export function EffectEditorHost(props) {
|
|
|
897
920
|
justifyContent: 'center',
|
|
898
921
|
background: 'rgba(7, 11, 22, 0.72)',
|
|
899
922
|
pointerEvents: 'none',
|
|
900
|
-
}, children: _jsxs("div", { style: {
|
|
923
|
+
}, children: _jsxs("div", { style: {
|
|
924
|
+
background: theme.panelBg,
|
|
925
|
+
border: `1px solid ${theme.border}`,
|
|
926
|
+
borderRadius: 10,
|
|
927
|
+
padding: '16px 22px',
|
|
928
|
+
textAlign: 'center',
|
|
929
|
+
}, children: [_jsx("div", { style: { fontWeight: 600, marginBottom: 6 }, children: "Loading gallery\u2026" }), _jsxs("div", { style: { fontSize: 13, color: theme.textDim }, children: [galleryLoading.done, " / ", galleryLoading.total || '…', " files", galleryLoading.failed > 0 ? ` · ${galleryLoading.failed} skipped` : ''] })] }) })), props.backLink && (_jsxs("a", { className: "qe-hover", href: props.backLink.href, style: {
|
|
901
930
|
position: 'absolute',
|
|
902
931
|
top: 12,
|
|
903
932
|
left: 12,
|
|
@@ -913,13 +942,74 @@ export function EffectEditorHost(props) {
|
|
|
913
942
|
color: theme.text,
|
|
914
943
|
textDecoration: 'none',
|
|
915
944
|
fontFamily: theme.font,
|
|
916
|
-
}, children: [_jsx(ArrowLeftIcon, { style: iconStyle(14) }), props.backLink.label.replace(/^←\s*/, '')] })), _jsxs("div", { style: {
|
|
945
|
+
}, children: [_jsx(ArrowLeftIcon, { style: iconStyle(14) }), props.backLink.label.replace(/^←\s*/, '')] })), _jsxs("div", { style: {
|
|
946
|
+
position: 'absolute',
|
|
947
|
+
top: 12,
|
|
948
|
+
right: 12,
|
|
949
|
+
display: 'flex',
|
|
950
|
+
flexDirection: 'column',
|
|
951
|
+
alignItems: 'flex-end',
|
|
952
|
+
gap: 6,
|
|
953
|
+
}, children: [_jsxs("div", { style: {
|
|
954
|
+
display: 'flex',
|
|
955
|
+
gap: 4,
|
|
956
|
+
background: theme.panelBg,
|
|
957
|
+
border: `1px solid ${theme.border}`,
|
|
958
|
+
borderRadius: 8,
|
|
959
|
+
padding: 4,
|
|
960
|
+
}, children: [_jsx("button", { className: "qe-hover", style: squareButtonStyle, title: "Undo (Ctrl+Z)", disabled: !state?.history.canUndo || !binding, onClick: () => historyAction(state.history.undo()), children: _jsx(ArrowUturnLeftIcon, { style: iconStyle(14) }) }), _jsx("button", { className: "qe-hover", style: squareButtonStyle, title: "Redo (Ctrl+Shift+Z)", disabled: !state?.history.canRedo || !binding, onClick: () => historyAction(state.history.redo()), children: _jsx(ArrowUturnRightIcon, { style: iconStyle(14) }) })] }), _jsx("div", { style: {
|
|
961
|
+
display: 'flex',
|
|
962
|
+
flexDirection: 'column',
|
|
963
|
+
gap: 4,
|
|
964
|
+
background: theme.panelBg,
|
|
965
|
+
border: `1px solid ${theme.border}`,
|
|
966
|
+
borderRadius: 8,
|
|
967
|
+
padding: 4,
|
|
968
|
+
}, children: GIZMO_MODE_BUTTONS.map((m) => (_jsx("button", { className: "qe-hover", style: {
|
|
969
|
+
...squareButtonStyle,
|
|
970
|
+
...(gizmoMode === m.value ? activeSquareButtonStyle : {}),
|
|
971
|
+
}, title: m.title, onClick: () => setGizmoMode(m.value), children: _jsx(m.Icon, { style: iconStyle(14) }) }, m.value))) }), _jsx("div", { style: {
|
|
972
|
+
display: 'flex',
|
|
973
|
+
flexDirection: 'column',
|
|
974
|
+
gap: 4,
|
|
975
|
+
background: theme.panelBg,
|
|
976
|
+
border: `1px solid ${theme.border}`,
|
|
977
|
+
borderRadius: 8,
|
|
978
|
+
padding: 4,
|
|
979
|
+
}, children: _jsx("button", { className: "qe-hover", style: {
|
|
980
|
+
...squareButtonStyle,
|
|
981
|
+
...(showEmitterWireframes ? activeSquareButtonStyle : {}),
|
|
982
|
+
}, title: "Show emitter shape wireframes", disabled: !binding, onClick: () => setShowEmitterWireframes((on) => !on), children: _jsx(CubeTransparentIcon, { style: iconStyle(14) }) }) })] })] }), _jsxs("aside", { style: {
|
|
983
|
+
width: 340,
|
|
984
|
+
flexShrink: 0,
|
|
985
|
+
borderLeft: `1px solid ${theme.border}`,
|
|
986
|
+
background: theme.panelBg,
|
|
987
|
+
display: 'flex',
|
|
988
|
+
flexDirection: 'column',
|
|
989
|
+
minHeight: 0,
|
|
990
|
+
}, children: [_jsxs("div", { style: { padding: '12px 14px 10px', borderBottom: `1px solid ${theme.border}` }, children: [_jsx("div", { style: {
|
|
991
|
+
fontSize: 11,
|
|
992
|
+
fontWeight: 600,
|
|
993
|
+
color: theme.textDim,
|
|
994
|
+
textTransform: 'uppercase',
|
|
995
|
+
letterSpacing: 0.4,
|
|
996
|
+
marginBottom: 6,
|
|
997
|
+
}, children: "Inspector" }), _jsx("div", { className: "qe-hover-bg", title: binding ? 'Rename effect' : undefined, style: {
|
|
998
|
+
fontSize: 16,
|
|
999
|
+
fontWeight: 600,
|
|
1000
|
+
cursor: binding ? 'pointer' : 'default',
|
|
1001
|
+
borderRadius: 6,
|
|
1002
|
+
padding: '2px 4px',
|
|
1003
|
+
margin: '-2px -4px',
|
|
1004
|
+
}, onClick: () => binding && setRenamingRoot(true), children: (binding?.root.name || props.title) ?? 'Effect editor' }), renamingRoot && binding && (_jsx(PromptDialog, { title: "Rename effect", defaultValue: binding.root.name, onSubmit: (name) => {
|
|
917
1005
|
binding.apply(() => (binding.root.name = name));
|
|
918
1006
|
setRenamingRoot(false);
|
|
919
1007
|
}, onCancel: () => setRenamingRoot(false) })), _jsxs("div", { style: { display: 'flex', flexWrap: 'wrap', gap: 6, marginTop: 10 }, children: [_jsx("button", { className: "qe-hover", style: buttonStyle, disabled: !binding, onClick: () => {
|
|
920
1008
|
if (!binding)
|
|
921
1009
|
return;
|
|
922
|
-
const blob = new Blob([binding.exportJSON('EditorEffect')], {
|
|
1010
|
+
const blob = new Blob([binding.exportJSON('EditorEffect')], {
|
|
1011
|
+
type: 'application/json',
|
|
1012
|
+
});
|
|
923
1013
|
const link = document.createElement('a');
|
|
924
1014
|
link.href = URL.createObjectURL(blob);
|
|
925
1015
|
link.download = 'effect.json';
|
|
@@ -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 { GalleryEntry, GalleryLoadProgress } from '../core/loadEffectGallery';
|
|
4
4
|
export interface GalleryPanelProps {
|
|
5
5
|
entries: GalleryEntry[];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useCallback, useMemo, useRef, useState } from 'react';
|
|
3
|
-
import { useVirtualizer } from '@tanstack/react-virtual';
|
|
4
2
|
import { CheckCircleIcon, ChevronDownIcon, ChevronRightIcon, FolderIcon, FolderOpenIcon, SparklesIcon, XMarkIcon, } from '@heroicons/react/24/solid';
|
|
3
|
+
import { useVirtualizer } from '@tanstack/react-virtual';
|
|
4
|
+
import { useCallback, useMemo, useRef, useState } from 'react';
|
|
5
5
|
import { setGalleryDragSession } from '../core/galleryDragSession';
|
|
6
6
|
import { buildGalleryTree, filterGalleryTree, flattenVisibleGalleryRows, writeGalleryDrag, } from '../core/galleryTree';
|
|
7
7
|
import { iconStyle } from './icons';
|
|
@@ -50,7 +50,13 @@ function EffectRow(props) {
|
|
|
50
50
|
}, onClick: (e) => {
|
|
51
51
|
e.stopPropagation();
|
|
52
52
|
onTogglePreview(entry);
|
|
53
|
-
}, children: inPreview ? (_jsx(CheckCircleIcon, { style: iconStyle(14) })) : (_jsx("span", { style: {
|
|
53
|
+
}, children: inPreview ? (_jsx(CheckCircleIcon, { style: iconStyle(14) })) : (_jsx("span", { style: {
|
|
54
|
+
width: 10,
|
|
55
|
+
height: 10,
|
|
56
|
+
border: `1px solid currentColor`,
|
|
57
|
+
borderRadius: 2,
|
|
58
|
+
boxSizing: 'border-box',
|
|
59
|
+
} })) })) : (_jsx("span", { style: { width: 28, flexShrink: 0, borderRight: `1px solid ${theme.border}` } })), _jsxs("button", { type: "button", draggable: true, className: "qe-hover-bg", title: entry.inScene
|
|
54
60
|
? `${entry.path} — on stage${inPreview ? ' · previewing' : ' · parked'} · Ctrl+click toggles preview`
|
|
55
61
|
: `${entry.path} — catalog only · drag into viewport to place`, style: {
|
|
56
62
|
display: 'flex',
|
|
@@ -129,7 +135,7 @@ function flatRowKey(row) {
|
|
|
129
135
|
return row.kind === 'folder' ? `folder:${row.node.path}` : `effect:${row.node.entry.root.uniqueId}`;
|
|
130
136
|
}
|
|
131
137
|
function VirtualGalleryTree(props) {
|
|
132
|
-
const { rows, closedFolders, focusedRoot, previewRootIds, onFocus, onTogglePreview, onRemoveFromScene, onToggleFolder } = props;
|
|
138
|
+
const { rows, closedFolders, focusedRoot, previewRootIds, onFocus, onTogglePreview, onRemoveFromScene, onToggleFolder, } = props;
|
|
133
139
|
const scrollRef = useRef(null);
|
|
134
140
|
const virtualizer = useVirtualizer({
|
|
135
141
|
count: rows.length,
|
|
@@ -159,7 +165,7 @@ function VirtualGalleryTree(props) {
|
|
|
159
165
|
}
|
|
160
166
|
/** Folder-tree catalog; import effects here and drag them into the viewport. */
|
|
161
167
|
export function GalleryPanel(props) {
|
|
162
|
-
const { entries, focusedRoot, previewRootIds, loading, folderInputRef, onFocus, onTogglePreview, onRemoveFromScene, onImportFiles, onImportFolder, onCreateDefault } = props;
|
|
168
|
+
const { entries, focusedRoot, previewRootIds, loading, folderInputRef, onFocus, onTogglePreview, onRemoveFromScene, onImportFiles, onImportFolder, onCreateDefault, } = props;
|
|
163
169
|
const [query, setQuery] = useState('');
|
|
164
170
|
const [closedFolders, setClosedFolders] = useState(() => new Set());
|
|
165
171
|
const fileInputRef = useRef(null);
|
|
@@ -204,7 +210,8 @@ export function GalleryPanel(props) {
|
|
|
204
210
|
cursor: isLoading ? 'not-allowed' : 'pointer',
|
|
205
211
|
opacity: isLoading ? 0.5 : 1,
|
|
206
212
|
}, onClick: (e) => {
|
|
207
|
-
if (typeof window.showDirectoryPicker ===
|
|
213
|
+
if (typeof window.showDirectoryPicker ===
|
|
214
|
+
'function') {
|
|
208
215
|
e.preventDefault();
|
|
209
216
|
onImportFolder();
|
|
210
217
|
}
|
|
@@ -233,7 +240,7 @@ export function GalleryPanel(props) {
|
|
|
233
240
|
border: `1px solid ${theme.border}`,
|
|
234
241
|
borderRadius: 8,
|
|
235
242
|
background: theme.sectionBg,
|
|
236
|
-
}, children: ["Catalog is empty. Click ", _jsx("strong", { children: "New effect" }), ", or ", _jsx("strong", { children: "Import effect" }), " /
|
|
243
|
+
}, children: ["Catalog is empty. Click ", _jsx("strong", { children: "New effect" }), ", or ", _jsx("strong", { children: "Import effect" }), " /", ' ', _jsx("strong", { children: "Import folder" }), "."] })) : filtered.length === 0 ? (_jsx("div", { style: {
|
|
237
244
|
flex: 1,
|
|
238
245
|
minHeight: 120,
|
|
239
246
|
padding: '10px 12px',
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
1
|
import { GradientStop } from '../core/colors';
|
|
3
2
|
/** Gradient editor: RGBA stops on a bar — click to add, drag to move, edit the selected stop. */
|
|
4
3
|
export declare function GradientEditor(props: {
|
|
5
4
|
stops: GradientStop[];
|
|
6
5
|
onChange: (stops: GradientStop[]) => void;
|
|
7
|
-
}):
|
|
6
|
+
}): import("react").JSX.Element;
|
|
@@ -65,7 +65,14 @@ export function GradientEditor(props) {
|
|
|
65
65
|
}, onPointerUp: (e) => {
|
|
66
66
|
dragging.current = false;
|
|
67
67
|
e.currentTarget.releasePointerCapture(e.pointerId);
|
|
68
|
-
} }, i))) }), current && (_jsxs(_Fragment, { children: [_jsx(Row, { label: "Stop color", children: _jsx("input", { type: "color", className: "qe-hover", value: rgbToHex(current.r, current.g, current.b), onChange: (e) => updateStop(selected, hexToRgb(e.target.value)), style: {
|
|
68
|
+
} }, i))) }), current && (_jsxs(_Fragment, { children: [_jsx(Row, { label: "Stop color", children: _jsx("input", { type: "color", className: "qe-hover", value: rgbToHex(current.r, current.g, current.b), onChange: (e) => updateStop(selected, hexToRgb(e.target.value)), style: {
|
|
69
|
+
width: 42,
|
|
70
|
+
height: 24,
|
|
71
|
+
border: 'none',
|
|
72
|
+
background: 'transparent',
|
|
73
|
+
padding: 0,
|
|
74
|
+
cursor: 'pointer',
|
|
75
|
+
} }) }), _jsx(Row, { label: "Stop alpha", children: _jsx(NumberField, { value: current.a, min: 0, step: 0.05, onChange: (a) => updateStop(selected, { a: Math.min(1, a) }) }) }), _jsx(Row, { label: "Stop position", children: _jsx(NumberField, { value: current.pos, min: 0, step: 0.05, onChange: (pos) => updateStop(selected, { pos: Math.min(1, pos) }) }) }), stops.length > 2 && (_jsx("button", { className: "qe-hover", style: { ...buttonStyle, marginTop: 6 }, onClick: () => {
|
|
69
76
|
props.onChange(stops.filter((_, i) => i !== selected));
|
|
70
77
|
setSelected(0);
|
|
71
78
|
}, children: "Remove stop" }))] }))] }));
|
|
@@ -20,5 +20,11 @@ export function MessageDialog(props) {
|
|
|
20
20
|
minWidth: 320,
|
|
21
21
|
maxWidth: 480,
|
|
22
22
|
boxShadow: '0 12px 32px rgba(0, 0, 0, 0.5)',
|
|
23
|
-
}, onClick: (e) => e.stopPropagation(), children: [_jsx("div", { style: { fontSize: 14, fontWeight: 600, color: theme.text, marginBottom: 8 }, children: title }), _jsx("div", { style: {
|
|
23
|
+
}, onClick: (e) => e.stopPropagation(), children: [_jsx("div", { style: { fontSize: 14, fontWeight: 600, color: theme.text, marginBottom: 8 }, children: title }), _jsx("div", { style: {
|
|
24
|
+
fontSize: 13,
|
|
25
|
+
lineHeight: 1.5,
|
|
26
|
+
color: theme.textDim,
|
|
27
|
+
marginBottom: 14,
|
|
28
|
+
whiteSpace: 'pre-wrap',
|
|
29
|
+
}, children: message }), _jsx("div", { style: { display: 'flex', justifyContent: 'flex-end' }, children: _jsx("button", { className: "qe-hover", style: { ...buttonStyle, background: theme.accent, color: '#0a1128', borderColor: theme.accent }, onClick: onClose, children: confirmLabel }) })] }) }));
|
|
24
30
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState } from 'react';
|
|
3
2
|
import { ChevronDownIcon, ChevronRightIcon } from '@heroicons/react/24/solid';
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { iconStyle } from './icons';
|
|
4
5
|
import { getInspectorHint } from './inspectorHints';
|
|
5
6
|
import { InspectorInfoIcon } from './InspectorInfoIcon';
|
|
6
7
|
import { theme } from './theme';
|
|
7
|
-
import { iconStyle } from './icons';
|
|
8
8
|
/** Unity-style module foldout with an optional enable checkbox. */
|
|
9
9
|
export function ModuleSection(props) {
|
|
10
10
|
const [open, setOpen] = useState(props.defaultOpen ?? true);
|
|
@@ -27,5 +27,12 @@ export function ModuleSection(props) {
|
|
|
27
27
|
cursor: 'pointer',
|
|
28
28
|
userSelect: 'none',
|
|
29
29
|
background: 'rgba(20, 30, 60, 0.5)',
|
|
30
|
-
}, onClick: () => setOpen(!open), children: [_jsx("span", { style: { color: theme.accent, display: 'inline-flex' }, children: open ? _jsx(ChevronDownIcon, { style: iconStyle(12) }) : _jsx(ChevronRightIcon, { style: iconStyle(12) }) }), toggleable && (_jsx("input", { type: "checkbox", className: "qe-hover", checked: props.enabled, onClick: (e) => e.stopPropagation(), onChange: (e) => props.onToggle?.(e.target.checked), style: { width: 14, height: 14, accentColor: '#78a5ff', cursor: 'pointer' } })), _jsxs("span", { style: {
|
|
30
|
+
}, onClick: () => setOpen(!open), children: [_jsx("span", { style: { color: theme.accent, display: 'inline-flex' }, children: open ? _jsx(ChevronDownIcon, { style: iconStyle(12) }) : _jsx(ChevronRightIcon, { style: iconStyle(12) }) }), toggleable && (_jsx("input", { type: "checkbox", className: "qe-hover", checked: props.enabled, onClick: (e) => e.stopPropagation(), onChange: (e) => props.onToggle?.(e.target.checked), style: { width: 14, height: 14, accentColor: '#78a5ff', cursor: 'pointer' } })), _jsxs("span", { style: {
|
|
31
|
+
color: dimmed ? theme.textDim : theme.text,
|
|
32
|
+
fontSize: 13,
|
|
33
|
+
fontWeight: 600,
|
|
34
|
+
display: 'inline-flex',
|
|
35
|
+
alignItems: 'center',
|
|
36
|
+
gap: 5,
|
|
37
|
+
}, children: [props.title, tooltip ? _jsx(InspectorInfoIcon, { content: tooltip, onClick: (e) => e.stopPropagation() }) : null] })] }), open && !dimmed && _jsx("div", { style: { padding: '4px 10px 10px' }, children: props.children })] }));
|
|
31
38
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useSyncExternalStore } from 'react';
|
|
3
2
|
import { Quaternion } from '@babylonjs/core/Maths/math.vector';
|
|
3
|
+
import { useSyncExternalStore } from 'react';
|
|
4
4
|
import { getTransformRevision, notifyTransformChanged, subscribeTransform } from '../core/transformStore';
|
|
5
5
|
import { ModuleSection } from './ModuleSection';
|
|
6
6
|
import { NumberField, Row } from './fields';
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
1
|
import { EffectBinding } from '../core/binding';
|
|
3
2
|
/**
|
|
4
3
|
* Sub Emitters module for the currently selected system. The spawned target lives as a
|
|
@@ -7,4 +6,4 @@ import { EffectBinding } from '../core/binding';
|
|
|
7
6
|
export declare function SubEmittersModule(props: {
|
|
8
7
|
binding: EffectBinding;
|
|
9
8
|
rootBinding: EffectBinding;
|
|
10
|
-
}):
|
|
9
|
+
}): import("react").JSX.Element;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import type { BatchedRenderer } from 'babylon.quarks';
|
|
3
|
-
import type { Scene } from '@babylonjs/core/scene';
|
|
4
1
|
import type { TransformNode } from '@babylonjs/core/Meshes/transformNode';
|
|
2
|
+
import type { Scene } from '@babylonjs/core/scene';
|
|
3
|
+
import type { BatchedRenderer } from 'babylon.quarks';
|
|
4
|
+
import React from 'react';
|
|
5
5
|
import type { EffectBinding } from '../core/binding';
|
|
6
6
|
import { type PlaybackState } from '../core/playback';
|
|
7
7
|
export type { PlaybackState } from '../core/playback';
|
|
@@ -1,8 +1,8 @@
|
|
|
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
8
|
import { TimelineTrackRow } from './TimelineTrackRow';
|
|
@@ -29,7 +29,7 @@ function pickTickInterval(pxPerSecond) {
|
|
|
29
29
|
* apply only to the focused effect without stopping other previews.
|
|
30
30
|
*/
|
|
31
31
|
export function TimelinePanel(props) {
|
|
32
|
-
const { binding, selectedNode, onSelectNode, scene, renderer, playbackRef, playheadRef, counterRef, stagePlaying, setStagePlaying, speed, setSpeed, playbackEnabled = true } = props;
|
|
32
|
+
const { binding, selectedNode, onSelectNode, scene, renderer, playbackRef, playheadRef, counterRef, stagePlaying, setStagePlaying, speed, setSpeed, playbackEnabled = true, } = props;
|
|
33
33
|
const [bodyWidthPx, setBodyWidthPx] = useState(600);
|
|
34
34
|
const [collapsedGroups, setCollapsedGroups] = useState(() => new Set());
|
|
35
35
|
const [hiddenSystems, setHiddenSystems] = useState(() => new Set());
|
|
@@ -129,7 +129,18 @@ export function TimelinePanel(props) {
|
|
|
129
129
|
};
|
|
130
130
|
raf = requestAnimationFrame(tick);
|
|
131
131
|
return () => cancelAnimationFrame(raf);
|
|
132
|
-
}, [
|
|
132
|
+
}, [
|
|
133
|
+
pxPerSecond,
|
|
134
|
+
timelineSpan,
|
|
135
|
+
anyLooping,
|
|
136
|
+
playbackRef,
|
|
137
|
+
playheadRef,
|
|
138
|
+
focusRootId,
|
|
139
|
+
playbackEnabled,
|
|
140
|
+
binding,
|
|
141
|
+
scene,
|
|
142
|
+
renderer,
|
|
143
|
+
]);
|
|
133
144
|
const visibleRows = useMemo(() => {
|
|
134
145
|
let hideUntilDepth = null;
|
|
135
146
|
const visible = rows.filter((row) => {
|
|
@@ -246,11 +257,23 @@ export function TimelinePanel(props) {
|
|
|
246
257
|
flexDirection: 'column',
|
|
247
258
|
fontFamily: theme.font,
|
|
248
259
|
color: theme.text,
|
|
249
|
-
}, children: [_jsxs("div", { style: {
|
|
260
|
+
}, children: [_jsxs("div", { style: {
|
|
261
|
+
display: 'flex',
|
|
262
|
+
alignItems: 'center',
|
|
263
|
+
gap: 6,
|
|
264
|
+
padding: '6px 10px',
|
|
265
|
+
borderBottom: `1px solid ${theme.border}`,
|
|
266
|
+
}, 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
267
|
const next = Number(e.target.value);
|
|
251
268
|
playbackRef.current.speed = next;
|
|
252
269
|
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: {
|
|
270
|
+
}, 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: {
|
|
271
|
+
fontSize: 12,
|
|
272
|
+
color: theme.textDim,
|
|
273
|
+
fontVariantNumeric: 'tabular-nums',
|
|
274
|
+
paddingLeft: 4,
|
|
275
|
+
minWidth: 120,
|
|
276
|
+
} }), 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
277
|
position: 'absolute',
|
|
255
278
|
inset: 0,
|
|
256
279
|
display: 'flex',
|
|
@@ -271,7 +294,12 @@ export function TimelinePanel(props) {
|
|
|
271
294
|
gridTemplateColumns: `${LABEL_WIDTH}px 1fr`,
|
|
272
295
|
background: theme.panelBg,
|
|
273
296
|
zIndex: 2,
|
|
274
|
-
}, children: [_jsx("div", { style: { height: RULER_HEIGHT } }), _jsx("div", { className: "qe-hover-bg", style: {
|
|
297
|
+
}, children: [_jsx("div", { style: { height: RULER_HEIGHT } }), _jsx("div", { className: "qe-hover-bg", style: {
|
|
298
|
+
position: 'relative',
|
|
299
|
+
height: RULER_HEIGHT,
|
|
300
|
+
cursor: 'ew-resize',
|
|
301
|
+
overflow: 'hidden',
|
|
302
|
+
}, onPointerDown: (e) => {
|
|
275
303
|
playbackRef.current.scrubbing = true;
|
|
276
304
|
e.target.setPointerCapture(e.pointerId);
|
|
277
305
|
queueScrub(e.clientX);
|
|
@@ -281,7 +309,16 @@ export function TimelinePanel(props) {
|
|
|
281
309
|
}, onPointerUp: (e) => {
|
|
282
310
|
playbackRef.current.scrubbing = false;
|
|
283
311
|
e.target.releasePointerCapture?.(e.pointerId);
|
|
284
|
-
}, children: ticks.map((t) => (_jsxs("div", { style: {
|
|
312
|
+
}, children: ticks.map((t) => (_jsxs("div", { style: {
|
|
313
|
+
position: 'absolute',
|
|
314
|
+
left: TIMELINE_INSET + t * pxPerSecond,
|
|
315
|
+
top: 0,
|
|
316
|
+
bottom: 0,
|
|
317
|
+
borderLeft: `1px solid ${theme.border}`,
|
|
318
|
+
fontSize: 10,
|
|
319
|
+
color: theme.textDim,
|
|
320
|
+
paddingLeft: 3,
|
|
321
|
+
}, children: [t, "s"] }, t))) })] }), _jsx("div", { style: {
|
|
285
322
|
position: 'absolute',
|
|
286
323
|
top: RULER_HEIGHT,
|
|
287
324
|
left: 0,
|
|
@@ -293,7 +330,16 @@ export function TimelinePanel(props) {
|
|
|
293
330
|
display: 'grid',
|
|
294
331
|
gridTemplateColumns: `${LABEL_WIDTH}px 1fr`,
|
|
295
332
|
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: {
|
|
333
|
+
}, 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: {
|
|
334
|
+
position: 'absolute',
|
|
335
|
+
top: 0,
|
|
336
|
+
bottom: 0,
|
|
337
|
+
left: LABEL_WIDTH,
|
|
338
|
+
width: 1,
|
|
339
|
+
background: theme.border,
|
|
340
|
+
pointerEvents: 'none',
|
|
341
|
+
zIndex: 3,
|
|
342
|
+
} }), _jsx("div", { ref: playheadRef, style: {
|
|
297
343
|
position: 'absolute',
|
|
298
344
|
top: 0,
|
|
299
345
|
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 {
|