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,47 +1,43 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import {ArcRotateCamera} from '@babylonjs/core/Cameras/arcRotateCamera';
|
|
2
|
+
import {Engine} from '@babylonjs/core/Engines/engine';
|
|
3
|
+
import {PointerEventTypes} from '@babylonjs/core/Events/pointerEvents';
|
|
4
|
+
import {GizmoManager} from '@babylonjs/core/Gizmos/gizmoManager';
|
|
5
|
+
import {DirectionalLight} from '@babylonjs/core/Lights/directionalLight';
|
|
6
|
+
import {Color3, Color4} from '@babylonjs/core/Maths/math.color';
|
|
7
|
+
import {Vector3 as BVector3} from '@babylonjs/core/Maths/math.vector';
|
|
8
|
+
import type {Mesh} from '@babylonjs/core/Meshes/mesh';
|
|
9
|
+
import {MeshBuilder} from '@babylonjs/core/Meshes/meshBuilder';
|
|
10
|
+
import {TransformNode} from '@babylonjs/core/Meshes/transformNode';
|
|
11
|
+
import {UtilityLayerRenderer} from '@babylonjs/core/Rendering/utilityLayerRenderer';
|
|
12
|
+
import {Scene} from '@babylonjs/core/scene';
|
|
13
|
+
import {GridMaterial} from '@babylonjs/materials/grid/gridMaterial';
|
|
3
14
|
import {
|
|
4
15
|
ArrowLeftIcon,
|
|
5
16
|
ArrowPathIcon,
|
|
17
|
+
ArrowsPointingOutIcon,
|
|
6
18
|
ArrowUturnLeftIcon,
|
|
7
19
|
ArrowUturnRightIcon,
|
|
8
|
-
ArrowsPointingOutIcon,
|
|
9
20
|
CubeTransparentIcon,
|
|
10
21
|
CursorArrowRaysIcon,
|
|
11
22
|
Square2StackIcon,
|
|
12
23
|
} from '@heroicons/react/24/solid';
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {ArcRotateCamera} from '@babylonjs/core/Cameras/arcRotateCamera';
|
|
17
|
-
import {DirectionalLight} from '@babylonjs/core/Lights/directionalLight';
|
|
18
|
-
import {Vector3 as BVector3} from '@babylonjs/core/Maths/math.vector';
|
|
19
|
-
import {Color3, Color4} from '@babylonjs/core/Maths/math.color';
|
|
20
|
-
import {MeshBuilder} from '@babylonjs/core/Meshes/meshBuilder';
|
|
21
|
-
import {TransformNode} from '@babylonjs/core/Meshes/transformNode';
|
|
22
|
-
import {GizmoManager} from '@babylonjs/core/Gizmos/gizmoManager';
|
|
23
|
-
import {UtilityLayerRenderer} from '@babylonjs/core/Rendering/utilityLayerRenderer';
|
|
24
|
-
import {GridMaterial} from '@babylonjs/materials/grid/gridMaterial';
|
|
25
|
-
import {
|
|
26
|
-
BatchedRenderer,
|
|
27
|
-
ParticleSystem,
|
|
28
|
-
QuarksUtil,
|
|
29
|
-
} from 'babylon.quarks';
|
|
24
|
+
import {BatchedRenderer, ParticleSystem, QuarksUtil} from 'babylon.quarks';
|
|
25
|
+
import React, {useCallback, useEffect, useMemo, useReducer, useRef, useState} from 'react';
|
|
26
|
+
import {createRoot} from 'react-dom/client';
|
|
30
27
|
import {EffectBinding} from '../core/binding';
|
|
31
|
-
import {EffectHistory} from '../core/history';
|
|
32
28
|
import {ensureGroundResolver} from '../core/collision';
|
|
33
29
|
import {createGalleryDefaultEntry} from '../core/defaultEffect';
|
|
34
|
-
import {loadEffectFromJson, disposeLoadedEffect, parseEffectFromJson} from '../core/loadEffect';
|
|
35
|
-
import {applyGalleryPlayback, countActivePreviews, countGalleryParticles, hasActivePreview, pauseFinishedPreviews} from '../core/galleryPlayback';
|
|
36
|
-
import {createPlaybackState, clearFocusFinished, getFocusElapsed, isFocusFinished, markFocusFinished, setFocusElapsed, type PlaybackState} from '../core/playback';
|
|
37
|
-
import {pauseFocus, scrubFocusToSettled} from '../core/scrub';
|
|
38
|
-
import {logRenderMismatch} from '../core/renderDebug';
|
|
39
|
-
import {cancelGalleryCameraFocus, focusGalleryEntry} from '../core/galleryLayout';
|
|
40
|
-
import {computeTimelineRows, computeTimelineSpan} from '../core/timeline';
|
|
41
|
-
import {GalleryDropMarker} from '../core/galleryDropMarker';
|
|
42
|
-
import {GallerySelectionMarker} from '../core/gallerySelectionMarker';
|
|
43
30
|
import {EmitterShapeWireframes} from '../core/emitterShapeWireframe';
|
|
44
31
|
import {getGalleryDragSession, setGalleryDragSession} from '../core/galleryDragSession';
|
|
32
|
+
import {GalleryDropMarker} from '../core/galleryDropMarker';
|
|
33
|
+
import {cancelGalleryCameraFocus, focusGalleryEntry} from '../core/galleryLayout';
|
|
34
|
+
import {
|
|
35
|
+
applyGalleryPlayback,
|
|
36
|
+
countActivePreviews,
|
|
37
|
+
countGalleryParticles,
|
|
38
|
+
hasActivePreview,
|
|
39
|
+
pauseFinishedPreviews,
|
|
40
|
+
} from '../core/galleryPlayback';
|
|
45
41
|
import {
|
|
46
42
|
clearGalleryEntrySimulation,
|
|
47
43
|
pickGroundPosition,
|
|
@@ -49,20 +45,41 @@ import {
|
|
|
49
45
|
placeGalleryEntryInScene,
|
|
50
46
|
removeGalleryEntryFromScene,
|
|
51
47
|
} from '../core/galleryScene';
|
|
48
|
+
import {GallerySelectionMarker} from '../core/gallerySelectionMarker';
|
|
52
49
|
import {describeGalleryDrag, GALLERY_DRAG_MIME, readGalleryDrag} from '../core/galleryTree';
|
|
53
|
-
import {
|
|
50
|
+
import {EffectHistory} from '../core/history';
|
|
51
|
+
import {disposeLoadedEffect, loadEffectFromJson, parseEffectFromJson} from '../core/loadEffect';
|
|
52
|
+
import {
|
|
53
|
+
bindingFromGalleryEntry,
|
|
54
|
+
collectJsonFiles,
|
|
55
|
+
loadEffectGallery,
|
|
56
|
+
mergeGalleryEntries,
|
|
57
|
+
type GalleryEntry,
|
|
58
|
+
type GalleryLoadProgress,
|
|
59
|
+
} from '../core/loadEffectGallery';
|
|
54
60
|
import {pickGalleryJsonFiles} from '../core/pickGalleryFiles';
|
|
55
|
-
import {
|
|
61
|
+
import {
|
|
62
|
+
clearFocusFinished,
|
|
63
|
+
createPlaybackState,
|
|
64
|
+
getFocusElapsed,
|
|
65
|
+
isFocusFinished,
|
|
66
|
+
markFocusFinished,
|
|
67
|
+
setFocusElapsed,
|
|
68
|
+
type PlaybackState,
|
|
69
|
+
} from '../core/playback';
|
|
70
|
+
import {logRenderMismatch} from '../core/renderDebug';
|
|
71
|
+
import {pauseFocus, scrubFocusToSettled} from '../core/scrub';
|
|
72
|
+
import {computeTimelineRows, computeTimelineSpan} from '../core/timeline';
|
|
56
73
|
import {notifyTransformChanged} from '../core/transformStore';
|
|
74
|
+
import {GalleryViewportPicker} from '../core/viewportPicker';
|
|
57
75
|
import {EffectEditor} from './EffectEditor';
|
|
58
|
-
import {PromptDialog} from './PromptDialog';
|
|
59
|
-
import {MessageDialog} from './MessageDialog';
|
|
60
76
|
import {GalleryPanel} from './GalleryPanel';
|
|
77
|
+
import {MessageDialog} from './MessageDialog';
|
|
78
|
+
import {PromptDialog} from './PromptDialog';
|
|
61
79
|
import {TimelinePanel} from './TimelinePanel';
|
|
80
|
+
import {iconStyle, type HeroIcon} from './icons';
|
|
62
81
|
import type {GeometryData, GeometryOption, TextureOption} from './modules';
|
|
63
82
|
import {buttonStyle, globalEditorStyles, theme} from './theme';
|
|
64
|
-
import {iconStyle, type HeroIcon} from './icons';
|
|
65
|
-
import type {Mesh} from '@babylonjs/core/Meshes/mesh';
|
|
66
83
|
|
|
67
84
|
export interface EffectEditorHostHandle {
|
|
68
85
|
binding: EffectBinding;
|
|
@@ -145,7 +162,9 @@ export function EffectEditorHost(props: EffectEditorHostProps) {
|
|
|
145
162
|
const selectGalleryEntryRef = useRef<(entry: GalleryEntry) => void>(() => {});
|
|
146
163
|
const toggleGalleryPreviewRef = useRef<(entry: GalleryEntry) => void>(() => {});
|
|
147
164
|
const removeGalleryFromSceneRef = useRef<(entry: GalleryEntry) => void>(() => {});
|
|
148
|
-
const placeGalleryDropRef = useRef<(clientX: number, clientY: number, dataTransfer?: DataTransfer) => void>(
|
|
165
|
+
const placeGalleryDropRef = useRef<(clientX: number, clientY: number, dataTransfer?: DataTransfer) => void>(
|
|
166
|
+
() => {}
|
|
167
|
+
);
|
|
149
168
|
const createGalleryDefaultRef = useRef<() => void>(() => {});
|
|
150
169
|
const dropMarkerRef = useRef<GalleryDropMarker | null>(null);
|
|
151
170
|
const selectionMarkerRef = useRef<GallerySelectionMarker | null>(null);
|
|
@@ -478,7 +497,9 @@ export function EffectEditorHost(props: EffectEditorHostProps) {
|
|
|
478
497
|
const appendGalleryFromFiles = async (files: FileList | File[]) => {
|
|
479
498
|
setGalleryLoading({done: 0, total: 0, loaded: 0, failed: 0});
|
|
480
499
|
|
|
481
|
-
const incoming = await loadEffectGallery(scene, files, galleryRoot, (progress) =>
|
|
500
|
+
const incoming = await loadEffectGallery(scene, files, galleryRoot, (progress) =>
|
|
501
|
+
setGalleryLoading(progress)
|
|
502
|
+
);
|
|
482
503
|
const {merged, added} = mergeGalleryEntries(galleryEntries, incoming, renderer);
|
|
483
504
|
|
|
484
505
|
galleryEntries = merged;
|
|
@@ -541,10 +562,19 @@ export function EffectEditorHost(props: EffectEditorHostProps) {
|
|
|
541
562
|
}
|
|
542
563
|
if (current) {
|
|
543
564
|
const catalogEntry = stateRef.current?.galleryEntries.find((entry) => entry.root === current.root);
|
|
544
|
-
disposeEffect(
|
|
565
|
+
disposeEffect(
|
|
566
|
+
current.root,
|
|
567
|
+
[current.system, ...current.subSystems],
|
|
568
|
+
renderer,
|
|
569
|
+
catalogEntry?.inScene ?? true
|
|
570
|
+
);
|
|
545
571
|
}
|
|
546
572
|
const main = systems.find((s) => !s.onlyUsedByOther) ?? systems[0];
|
|
547
|
-
const next = new EffectBinding(
|
|
573
|
+
const next = new EffectBinding(
|
|
574
|
+
main,
|
|
575
|
+
systems.filter((s) => s !== main),
|
|
576
|
+
root
|
|
577
|
+
);
|
|
548
578
|
setSelectedGalleryRoot(null);
|
|
549
579
|
setPreviewRootIds([]);
|
|
550
580
|
selectionMarker.hide();
|
|
@@ -667,7 +697,11 @@ export function EffectEditorHost(props: EffectEditorHostProps) {
|
|
|
667
697
|
|
|
668
698
|
const previewIds = new Set(snapshot.previewRootIds);
|
|
669
699
|
const binding = snapshot.binding;
|
|
670
|
-
if (
|
|
700
|
+
if (
|
|
701
|
+
binding &&
|
|
702
|
+
(binding.root.uniqueId !== focusTimelineMetaRef.current.rootId ||
|
|
703
|
+
binding.getRevision() !== focusTimelineMetaRef.current.revision)
|
|
704
|
+
) {
|
|
671
705
|
const rows = computeTimelineRows(binding);
|
|
672
706
|
focusTimelineMetaRef.current = {
|
|
673
707
|
rootId: binding.root.uniqueId,
|
|
@@ -787,7 +821,14 @@ export function EffectEditorHost(props: EffectEditorHostProps) {
|
|
|
787
821
|
gm.positionGizmoEnabled = gizmoMode === 'position';
|
|
788
822
|
gm.rotationGizmoEnabled = gizmoMode === 'rotation';
|
|
789
823
|
gm.scaleGizmoEnabled = gizmoMode === 'scale';
|
|
790
|
-
const activeGizmo =
|
|
824
|
+
const activeGizmo =
|
|
825
|
+
gizmoMode === 'position'
|
|
826
|
+
? gm.gizmos.positionGizmo
|
|
827
|
+
: gizmoMode === 'rotation'
|
|
828
|
+
? gm.gizmos.rotationGizmo
|
|
829
|
+
: gizmoMode === 'scale'
|
|
830
|
+
? gm.gizmos.scaleGizmo
|
|
831
|
+
: null;
|
|
791
832
|
let dragRaf = 0;
|
|
792
833
|
const scheduleTransformRefresh = () => {
|
|
793
834
|
if (dragRaf !== 0) {
|
|
@@ -872,7 +913,11 @@ export function EffectEditorHost(props: EffectEditorHostProps) {
|
|
|
872
913
|
root.name = prev.name;
|
|
873
914
|
}
|
|
874
915
|
const main = systems.find((s) => !s.onlyUsedByOther) ?? systems[0];
|
|
875
|
-
const next = new EffectBinding(
|
|
916
|
+
const next = new EffectBinding(
|
|
917
|
+
main,
|
|
918
|
+
systems.filter((s) => s !== main),
|
|
919
|
+
root
|
|
920
|
+
);
|
|
876
921
|
const oldRootId = current.root.uniqueId;
|
|
877
922
|
const updated = [...state.galleryEntries];
|
|
878
923
|
updated[entryIndex] = {name: prev.name, path: prev.path, root, systems, inScene: wasInScene};
|
|
@@ -905,7 +950,11 @@ export function EffectEditorHost(props: EffectEditorHostProps) {
|
|
|
905
950
|
}
|
|
906
951
|
disposeEffect(current.root, [current.system, ...current.subSystems], state.renderer);
|
|
907
952
|
const main = systems.find((s) => !s.onlyUsedByOther) ?? systems[0];
|
|
908
|
-
const next = new EffectBinding(
|
|
953
|
+
const next = new EffectBinding(
|
|
954
|
+
main,
|
|
955
|
+
systems.filter((s) => s !== main),
|
|
956
|
+
root
|
|
957
|
+
);
|
|
909
958
|
next.root.parent = null;
|
|
910
959
|
state.history.attach(next);
|
|
911
960
|
next.subscribe(force);
|
|
@@ -927,7 +976,7 @@ export function EffectEditorHost(props: EffectEditorHostProps) {
|
|
|
927
976
|
const activeSystem = binding ? (allSystems.find((s) => s.emitter === selectedNode) ?? binding.system) : null;
|
|
928
977
|
const gizmoTargetLabel = gizmoNode?.name || 'Node';
|
|
929
978
|
const selectedGalleryEntry = selectedGalleryRoot
|
|
930
|
-
? gallery.find((entry) => entry.root === selectedGalleryRoot) ?? null
|
|
979
|
+
? (gallery.find((entry) => entry.root === selectedGalleryRoot) ?? null)
|
|
931
980
|
: null;
|
|
932
981
|
/** Timeline is always shown when an effect is selected; playback only when it is on stage. */
|
|
933
982
|
const showTimeline = Boolean(binding && activeSystem && state);
|
|
@@ -952,7 +1001,18 @@ export function EffectEditorHost(props: EffectEditorHostProps) {
|
|
|
952
1001
|
);
|
|
953
1002
|
|
|
954
1003
|
return (
|
|
955
|
-
<div
|
|
1004
|
+
<div
|
|
1005
|
+
className="qe-root"
|
|
1006
|
+
style={{
|
|
1007
|
+
display: 'flex',
|
|
1008
|
+
flexDirection: 'column',
|
|
1009
|
+
width: '100%',
|
|
1010
|
+
height: '100%',
|
|
1011
|
+
fontFamily: theme.font,
|
|
1012
|
+
color: theme.text,
|
|
1013
|
+
background: '#070b16',
|
|
1014
|
+
}}
|
|
1015
|
+
>
|
|
956
1016
|
<style>{globalEditorStyles}</style>
|
|
957
1017
|
<div style={{display: 'flex', flex: 1, minHeight: 0}}>
|
|
958
1018
|
<aside
|
|
@@ -974,7 +1034,16 @@ export function EffectEditorHost(props: EffectEditorHostProps) {
|
|
|
974
1034
|
Drag into viewport to place · ✓ preview · × remove from stage
|
|
975
1035
|
</div>
|
|
976
1036
|
</div>
|
|
977
|
-
<div
|
|
1037
|
+
<div
|
|
1038
|
+
style={{
|
|
1039
|
+
flex: 1,
|
|
1040
|
+
minHeight: 0,
|
|
1041
|
+
overflow: 'hidden',
|
|
1042
|
+
padding: '8px 12px 14px',
|
|
1043
|
+
display: 'flex',
|
|
1044
|
+
flexDirection: 'column',
|
|
1045
|
+
}}
|
|
1046
|
+
>
|
|
978
1047
|
<GalleryPanel
|
|
979
1048
|
entries={gallery}
|
|
980
1049
|
focusedRoot={selectedGalleryRoot}
|
|
@@ -1028,7 +1097,10 @@ export function EffectEditorHost(props: EffectEditorHostProps) {
|
|
|
1028
1097
|
placeGalleryDropRef.current(e.clientX, e.clientY, e.dataTransfer);
|
|
1029
1098
|
}}
|
|
1030
1099
|
>
|
|
1031
|
-
<canvas
|
|
1100
|
+
<canvas
|
|
1101
|
+
ref={canvasRef}
|
|
1102
|
+
style={{width: '100%', height: '100%', display: 'block', touchAction: 'none', outline: 'none'}}
|
|
1103
|
+
/>
|
|
1032
1104
|
<div
|
|
1033
1105
|
style={{
|
|
1034
1106
|
position: 'absolute',
|
|
@@ -1073,7 +1145,8 @@ export function EffectEditorHost(props: EffectEditorHostProps) {
|
|
|
1073
1145
|
whiteSpace: 'nowrap',
|
|
1074
1146
|
}}
|
|
1075
1147
|
>
|
|
1076
|
-
Drop {galleryDragHover.count === 1 ? 'effect' : `${galleryDragHover.count} effects`} —
|
|
1148
|
+
Drop {galleryDragHover.count === 1 ? 'effect' : `${galleryDragHover.count} effects`} —{' '}
|
|
1149
|
+
{galleryDragHover.label}
|
|
1077
1150
|
</div>
|
|
1078
1151
|
</div>
|
|
1079
1152
|
)}
|
|
@@ -1089,7 +1162,15 @@ export function EffectEditorHost(props: EffectEditorHostProps) {
|
|
|
1089
1162
|
pointerEvents: 'none',
|
|
1090
1163
|
}}
|
|
1091
1164
|
>
|
|
1092
|
-
<div
|
|
1165
|
+
<div
|
|
1166
|
+
style={{
|
|
1167
|
+
background: theme.panelBg,
|
|
1168
|
+
border: `1px solid ${theme.border}`,
|
|
1169
|
+
borderRadius: 10,
|
|
1170
|
+
padding: '16px 22px',
|
|
1171
|
+
textAlign: 'center',
|
|
1172
|
+
}}
|
|
1173
|
+
>
|
|
1093
1174
|
<div style={{fontWeight: 600, marginBottom: 6}}>Loading gallery…</div>
|
|
1094
1175
|
<div style={{fontSize: 13, color: theme.textDim}}>
|
|
1095
1176
|
{galleryLoading.done} / {galleryLoading.total || '…'} files
|
|
@@ -1124,21 +1205,65 @@ export function EffectEditorHost(props: EffectEditorHostProps) {
|
|
|
1124
1205
|
{props.backLink.label.replace(/^←\s*/, '')}
|
|
1125
1206
|
</a>
|
|
1126
1207
|
)}
|
|
1127
|
-
<div
|
|
1128
|
-
|
|
1129
|
-
|
|
1208
|
+
<div
|
|
1209
|
+
style={{
|
|
1210
|
+
position: 'absolute',
|
|
1211
|
+
top: 12,
|
|
1212
|
+
right: 12,
|
|
1213
|
+
display: 'flex',
|
|
1214
|
+
flexDirection: 'column',
|
|
1215
|
+
alignItems: 'flex-end',
|
|
1216
|
+
gap: 6,
|
|
1217
|
+
}}
|
|
1218
|
+
>
|
|
1219
|
+
<div
|
|
1220
|
+
style={{
|
|
1221
|
+
display: 'flex',
|
|
1222
|
+
gap: 4,
|
|
1223
|
+
background: theme.panelBg,
|
|
1224
|
+
border: `1px solid ${theme.border}`,
|
|
1225
|
+
borderRadius: 8,
|
|
1226
|
+
padding: 4,
|
|
1227
|
+
}}
|
|
1228
|
+
>
|
|
1229
|
+
<button
|
|
1230
|
+
className="qe-hover"
|
|
1231
|
+
style={squareButtonStyle}
|
|
1232
|
+
title="Undo (Ctrl+Z)"
|
|
1233
|
+
disabled={!state?.history.canUndo || !binding}
|
|
1234
|
+
onClick={() => historyAction(state!.history.undo())}
|
|
1235
|
+
>
|
|
1130
1236
|
<ArrowUturnLeftIcon style={iconStyle(14)} />
|
|
1131
1237
|
</button>
|
|
1132
|
-
<button
|
|
1238
|
+
<button
|
|
1239
|
+
className="qe-hover"
|
|
1240
|
+
style={squareButtonStyle}
|
|
1241
|
+
title="Redo (Ctrl+Shift+Z)"
|
|
1242
|
+
disabled={!state?.history.canRedo || !binding}
|
|
1243
|
+
onClick={() => historyAction(state!.history.redo())}
|
|
1244
|
+
>
|
|
1133
1245
|
<ArrowUturnRightIcon style={iconStyle(14)} />
|
|
1134
1246
|
</button>
|
|
1135
1247
|
</div>
|
|
1136
|
-
<div
|
|
1248
|
+
<div
|
|
1249
|
+
style={{
|
|
1250
|
+
display: 'flex',
|
|
1251
|
+
flexDirection: 'column',
|
|
1252
|
+
gap: 4,
|
|
1253
|
+
background: theme.panelBg,
|
|
1254
|
+
border: `1px solid ${theme.border}`,
|
|
1255
|
+
borderRadius: 8,
|
|
1256
|
+
padding: 4,
|
|
1257
|
+
}}
|
|
1258
|
+
>
|
|
1137
1259
|
{GIZMO_MODE_BUTTONS.map((m) => (
|
|
1138
1260
|
<button
|
|
1139
1261
|
key={m.value}
|
|
1140
1262
|
className="qe-hover"
|
|
1141
|
-
style={{
|
|
1263
|
+
style={{
|
|
1264
|
+
...squareButtonStyle,
|
|
1265
|
+
...(gizmoMode === m.value ? activeSquareButtonStyle : {}),
|
|
1266
|
+
}}
|
|
1142
1267
|
title={m.title}
|
|
1143
1268
|
onClick={() => setGizmoMode(m.value)}
|
|
1144
1269
|
>
|
|
@@ -1146,10 +1271,23 @@ export function EffectEditorHost(props: EffectEditorHostProps) {
|
|
|
1146
1271
|
</button>
|
|
1147
1272
|
))}
|
|
1148
1273
|
</div>
|
|
1149
|
-
<div
|
|
1274
|
+
<div
|
|
1275
|
+
style={{
|
|
1276
|
+
display: 'flex',
|
|
1277
|
+
flexDirection: 'column',
|
|
1278
|
+
gap: 4,
|
|
1279
|
+
background: theme.panelBg,
|
|
1280
|
+
border: `1px solid ${theme.border}`,
|
|
1281
|
+
borderRadius: 8,
|
|
1282
|
+
padding: 4,
|
|
1283
|
+
}}
|
|
1284
|
+
>
|
|
1150
1285
|
<button
|
|
1151
1286
|
className="qe-hover"
|
|
1152
|
-
style={{
|
|
1287
|
+
style={{
|
|
1288
|
+
...squareButtonStyle,
|
|
1289
|
+
...(showEmitterWireframes ? activeSquareButtonStyle : {}),
|
|
1290
|
+
}}
|
|
1153
1291
|
title="Show emitter shape wireframes"
|
|
1154
1292
|
disabled={!binding}
|
|
1155
1293
|
onClick={() => setShowEmitterWireframes((on) => !on)}
|
|
@@ -1159,15 +1297,41 @@ export function EffectEditorHost(props: EffectEditorHostProps) {
|
|
|
1159
1297
|
</div>
|
|
1160
1298
|
</div>
|
|
1161
1299
|
</div>
|
|
1162
|
-
<aside
|
|
1300
|
+
<aside
|
|
1301
|
+
style={{
|
|
1302
|
+
width: 340,
|
|
1303
|
+
flexShrink: 0,
|
|
1304
|
+
borderLeft: `1px solid ${theme.border}`,
|
|
1305
|
+
background: theme.panelBg,
|
|
1306
|
+
display: 'flex',
|
|
1307
|
+
flexDirection: 'column',
|
|
1308
|
+
minHeight: 0,
|
|
1309
|
+
}}
|
|
1310
|
+
>
|
|
1163
1311
|
<div style={{padding: '12px 14px 10px', borderBottom: `1px solid ${theme.border}`}}>
|
|
1164
|
-
<div
|
|
1312
|
+
<div
|
|
1313
|
+
style={{
|
|
1314
|
+
fontSize: 11,
|
|
1315
|
+
fontWeight: 600,
|
|
1316
|
+
color: theme.textDim,
|
|
1317
|
+
textTransform: 'uppercase',
|
|
1318
|
+
letterSpacing: 0.4,
|
|
1319
|
+
marginBottom: 6,
|
|
1320
|
+
}}
|
|
1321
|
+
>
|
|
1165
1322
|
Inspector
|
|
1166
1323
|
</div>
|
|
1167
1324
|
<div
|
|
1168
1325
|
className="qe-hover-bg"
|
|
1169
1326
|
title={binding ? 'Rename effect' : undefined}
|
|
1170
|
-
style={{
|
|
1327
|
+
style={{
|
|
1328
|
+
fontSize: 16,
|
|
1329
|
+
fontWeight: 600,
|
|
1330
|
+
cursor: binding ? 'pointer' : 'default',
|
|
1331
|
+
borderRadius: 6,
|
|
1332
|
+
padding: '2px 4px',
|
|
1333
|
+
margin: '-2px -4px',
|
|
1334
|
+
}}
|
|
1171
1335
|
onClick={() => binding && setRenamingRoot(true)}
|
|
1172
1336
|
>
|
|
1173
1337
|
{(binding?.root.name || props.title) ?? 'Effect editor'}
|
|
@@ -1190,7 +1354,9 @@ export function EffectEditorHost(props: EffectEditorHostProps) {
|
|
|
1190
1354
|
disabled={!binding}
|
|
1191
1355
|
onClick={() => {
|
|
1192
1356
|
if (!binding) return;
|
|
1193
|
-
const blob = new Blob([binding.exportJSON('EditorEffect')], {
|
|
1357
|
+
const blob = new Blob([binding.exportJSON('EditorEffect')], {
|
|
1358
|
+
type: 'application/json',
|
|
1359
|
+
});
|
|
1194
1360
|
const link = document.createElement('a');
|
|
1195
1361
|
link.href = URL.createObjectURL(blob);
|
|
1196
1362
|
link.download = 'effect.json';
|
|
@@ -1201,7 +1367,12 @@ export function EffectEditorHost(props: EffectEditorHostProps) {
|
|
|
1201
1367
|
Export
|
|
1202
1368
|
</button>
|
|
1203
1369
|
{props.onSave && (
|
|
1204
|
-
<button
|
|
1370
|
+
<button
|
|
1371
|
+
className="qe-hover"
|
|
1372
|
+
style={buttonStyle}
|
|
1373
|
+
disabled={!binding}
|
|
1374
|
+
onClick={() => binding && props.onSave!(binding.exportJSON('EditorEffect'))}
|
|
1375
|
+
>
|
|
1205
1376
|
Save
|
|
1206
1377
|
</button>
|
|
1207
1378
|
)}
|
|
@@ -1243,7 +1414,11 @@ export function EffectEditorHost(props: EffectEditorHostProps) {
|
|
|
1243
1414
|
/>
|
|
1244
1415
|
)}
|
|
1245
1416
|
{messageDialog && (
|
|
1246
|
-
<MessageDialog
|
|
1417
|
+
<MessageDialog
|
|
1418
|
+
title={messageDialog.title}
|
|
1419
|
+
message={messageDialog.message}
|
|
1420
|
+
onClose={() => setMessageDialog(null)}
|
|
1421
|
+
/>
|
|
1247
1422
|
)}
|
|
1248
1423
|
</div>
|
|
1249
1424
|
);
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {useVirtualizer} from '@tanstack/react-virtual';
|
|
1
|
+
import type {TransformNode} from '@babylonjs/core/Meshes/transformNode';
|
|
3
2
|
import {
|
|
4
3
|
CheckCircleIcon,
|
|
5
4
|
ChevronDownIcon,
|
|
@@ -9,8 +8,8 @@ import {
|
|
|
9
8
|
SparklesIcon,
|
|
10
9
|
XMarkIcon,
|
|
11
10
|
} from '@heroicons/react/24/solid';
|
|
12
|
-
import
|
|
13
|
-
import
|
|
11
|
+
import {useVirtualizer} from '@tanstack/react-virtual';
|
|
12
|
+
import React, {useCallback, useMemo, useRef, useState} from 'react';
|
|
14
13
|
import {setGalleryDragSession} from '../core/galleryDragSession';
|
|
15
14
|
import {
|
|
16
15
|
buildGalleryTree,
|
|
@@ -20,6 +19,7 @@ import {
|
|
|
20
19
|
type GalleryFlatRow,
|
|
21
20
|
type GalleryTreeFolder,
|
|
22
21
|
} from '../core/galleryTree';
|
|
22
|
+
import type {GalleryEntry, GalleryLoadProgress} from '../core/loadEffectGallery';
|
|
23
23
|
import {iconStyle} from './icons';
|
|
24
24
|
import {buttonStyle, hiddenFileInputStyle, inputStyle, theme} from './theme';
|
|
25
25
|
|
|
@@ -111,7 +111,15 @@ function EffectRow(props: {
|
|
|
111
111
|
{inPreview ? (
|
|
112
112
|
<CheckCircleIcon style={iconStyle(14)} />
|
|
113
113
|
) : (
|
|
114
|
-
<span
|
|
114
|
+
<span
|
|
115
|
+
style={{
|
|
116
|
+
width: 10,
|
|
117
|
+
height: 10,
|
|
118
|
+
border: `1px solid currentColor`,
|
|
119
|
+
borderRadius: 2,
|
|
120
|
+
boxSizing: 'border-box',
|
|
121
|
+
}}
|
|
122
|
+
/>
|
|
115
123
|
)}
|
|
116
124
|
</button>
|
|
117
125
|
) : (
|
|
@@ -166,7 +174,10 @@ function EffectRow(props: {
|
|
|
166
174
|
</span>
|
|
167
175
|
<span style={{overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap'}}>{entry.name}</span>
|
|
168
176
|
{parkedOnStage && (
|
|
169
|
-
<span
|
|
177
|
+
<span
|
|
178
|
+
title="On stage but not in preview"
|
|
179
|
+
style={{flexShrink: 0, fontSize: 10, color: theme.textDim, letterSpacing: 0.3}}
|
|
180
|
+
>
|
|
170
181
|
parked
|
|
171
182
|
</span>
|
|
172
183
|
)}
|
|
@@ -258,7 +269,9 @@ function FolderRow(props: {
|
|
|
258
269
|
<span style={{width: 14, display: 'inline-flex', color: theme.accent}}>
|
|
259
270
|
{open ? <FolderOpenIcon style={iconStyle(14)} /> : <FolderIcon style={iconStyle(14)} />}
|
|
260
271
|
</span>
|
|
261
|
-
<span style={{flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap'}}>
|
|
272
|
+
<span style={{flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap'}}>
|
|
273
|
+
{node.name}
|
|
274
|
+
</span>
|
|
262
275
|
{effectCount > 0 && (
|
|
263
276
|
<span style={{fontSize: 11, fontWeight: 500, color: theme.textDim, flexShrink: 0}}>{effectCount}</span>
|
|
264
277
|
)}
|
|
@@ -280,7 +293,16 @@ function VirtualGalleryTree(props: {
|
|
|
280
293
|
onRemoveFromScene: (entry: GalleryEntry) => void;
|
|
281
294
|
onToggleFolder: (path: string) => void;
|
|
282
295
|
}): React.ReactElement {
|
|
283
|
-
const {
|
|
296
|
+
const {
|
|
297
|
+
rows,
|
|
298
|
+
closedFolders,
|
|
299
|
+
focusedRoot,
|
|
300
|
+
previewRootIds,
|
|
301
|
+
onFocus,
|
|
302
|
+
onTogglePreview,
|
|
303
|
+
onRemoveFromScene,
|
|
304
|
+
onToggleFolder,
|
|
305
|
+
} = props;
|
|
284
306
|
const scrollRef = useRef<HTMLDivElement>(null);
|
|
285
307
|
const virtualizer = useVirtualizer({
|
|
286
308
|
count: rows.length,
|
|
@@ -346,7 +368,19 @@ function VirtualGalleryTree(props: {
|
|
|
346
368
|
|
|
347
369
|
/** Folder-tree catalog; import effects here and drag them into the viewport. */
|
|
348
370
|
export function GalleryPanel(props: GalleryPanelProps): React.ReactElement {
|
|
349
|
-
const {
|
|
371
|
+
const {
|
|
372
|
+
entries,
|
|
373
|
+
focusedRoot,
|
|
374
|
+
previewRootIds,
|
|
375
|
+
loading,
|
|
376
|
+
folderInputRef,
|
|
377
|
+
onFocus,
|
|
378
|
+
onTogglePreview,
|
|
379
|
+
onRemoveFromScene,
|
|
380
|
+
onImportFiles,
|
|
381
|
+
onImportFolder,
|
|
382
|
+
onCreateDefault,
|
|
383
|
+
} = props;
|
|
350
384
|
const [query, setQuery] = useState('');
|
|
351
385
|
const [closedFolders, setClosedFolders] = useState<Set<string>>(() => new Set());
|
|
352
386
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
@@ -380,7 +414,13 @@ export function GalleryPanel(props: GalleryPanelProps): React.ReactElement {
|
|
|
380
414
|
return (
|
|
381
415
|
<div style={{display: 'flex', flexDirection: 'column', gap: 10, height: '100%', minHeight: 0}}>
|
|
382
416
|
<div style={{display: 'flex', flexWrap: 'wrap', gap: 6}}>
|
|
383
|
-
<button
|
|
417
|
+
<button
|
|
418
|
+
type="button"
|
|
419
|
+
className="qe-hover"
|
|
420
|
+
style={buttonStyle}
|
|
421
|
+
disabled={isLoading}
|
|
422
|
+
onClick={onCreateDefault}
|
|
423
|
+
>
|
|
384
424
|
New effect
|
|
385
425
|
</button>
|
|
386
426
|
<label
|
|
@@ -422,7 +462,10 @@ export function GalleryPanel(props: GalleryPanelProps): React.ReactElement {
|
|
|
422
462
|
opacity: isLoading ? 0.5 : 1,
|
|
423
463
|
}}
|
|
424
464
|
onClick={(e) => {
|
|
425
|
-
if (
|
|
465
|
+
if (
|
|
466
|
+
typeof (window as Window & {showDirectoryPicker?: unknown}).showDirectoryPicker ===
|
|
467
|
+
'function'
|
|
468
|
+
) {
|
|
426
469
|
e.preventDefault();
|
|
427
470
|
onImportFolder();
|
|
428
471
|
}
|
|
@@ -483,7 +526,8 @@ export function GalleryPanel(props: GalleryPanelProps): React.ReactElement {
|
|
|
483
526
|
background: theme.sectionBg,
|
|
484
527
|
}}
|
|
485
528
|
>
|
|
486
|
-
Catalog is empty. Click <strong>New effect</strong>, or <strong>Import effect</strong> /
|
|
529
|
+
Catalog is empty. Click <strong>New effect</strong>, or <strong>Import effect</strong> /{' '}
|
|
530
|
+
<strong>Import folder</strong>.
|
|
487
531
|
</div>
|
|
488
532
|
) : filtered.length === 0 ? (
|
|
489
533
|
<div
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {useRef, useState} from 'react';
|
|
2
2
|
import {GradientStop, hexToRgb, rgbToHex, stopsToCssGradient} from '../core/colors';
|
|
3
3
|
import {NumberField, Row} from './fields';
|
|
4
4
|
import {buttonStyle, theme} from './theme';
|
|
@@ -99,14 +99,31 @@ export function GradientEditor(props: {stops: GradientStop[]; onChange: (stops:
|
|
|
99
99
|
className="qe-hover"
|
|
100
100
|
value={rgbToHex(current.r, current.g, current.b)}
|
|
101
101
|
onChange={(e) => updateStop(selected, hexToRgb(e.target.value))}
|
|
102
|
-
style={{
|
|
102
|
+
style={{
|
|
103
|
+
width: 42,
|
|
104
|
+
height: 24,
|
|
105
|
+
border: 'none',
|
|
106
|
+
background: 'transparent',
|
|
107
|
+
padding: 0,
|
|
108
|
+
cursor: 'pointer',
|
|
109
|
+
}}
|
|
103
110
|
/>
|
|
104
111
|
</Row>
|
|
105
112
|
<Row label="Stop alpha">
|
|
106
|
-
<NumberField
|
|
113
|
+
<NumberField
|
|
114
|
+
value={current.a}
|
|
115
|
+
min={0}
|
|
116
|
+
step={0.05}
|
|
117
|
+
onChange={(a) => updateStop(selected, {a: Math.min(1, a)})}
|
|
118
|
+
/>
|
|
107
119
|
</Row>
|
|
108
120
|
<Row label="Stop position">
|
|
109
|
-
<NumberField
|
|
121
|
+
<NumberField
|
|
122
|
+
value={current.pos}
|
|
123
|
+
min={0}
|
|
124
|
+
step={0.05}
|
|
125
|
+
onChange={(pos) => updateStop(selected, {pos: Math.min(1, pos)})}
|
|
126
|
+
/>
|
|
110
127
|
</Row>
|
|
111
128
|
{stops.length > 2 && (
|
|
112
129
|
<button
|