babylon.quarks-editor 0.27.0 → 0.28.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 +71 -0
- package/README.md +31 -4
- package/dist/core/assetTree.d.ts +5 -0
- package/dist/core/assets.d.ts +7 -0
- package/dist/core/assets.js +11 -7
- package/dist/core/binding.d.ts +9 -0
- package/dist/core/binding.js +8 -0
- package/dist/core/collision.d.ts +4 -0
- package/dist/core/collision.js +4 -0
- package/dist/core/colors.d.ts +16 -0
- package/dist/core/colors.js +8 -0
- package/dist/core/defaultEffect.d.ts +3 -2
- package/dist/core/defaultEffect.js +1 -1
- package/dist/core/editorSession.d.ts +229 -0
- package/dist/core/editorSession.js +983 -0
- package/dist/core/effectTree.d.ts +5 -0
- package/dist/core/galleryPlayback.js +2 -1
- package/dist/core/history.d.ts +6 -0
- package/dist/core/history.js +4 -0
- package/dist/core/indexedDbProjectStore.d.ts +13 -0
- package/dist/core/indexedDbProjectStore.js +13 -0
- package/dist/core/material.d.ts +12 -2
- package/dist/core/playback.d.ts +6 -0
- package/dist/core/projectStore.d.ts +26 -0
- package/dist/core/projectStore.js +1 -0
- package/dist/core/sceneEntries.d.ts +3 -0
- package/dist/core/scrub.d.ts +1 -1
- package/dist/core/scrub.js +2 -2
- package/dist/core/shapes.d.ts +24 -0
- package/dist/core/shapes.js +5 -0
- package/dist/core/systems.d.ts +2 -0
- package/dist/core/systems.js +3 -1
- package/dist/core/timeline.d.ts +23 -3
- package/dist/core/values.d.ts +21 -4
- package/dist/core/values.js +5 -0
- package/dist/index.d.ts +5 -1
- package/dist/react/AssetsPanel.d.ts +7 -0
- package/dist/react/ChoiceDialog.d.ts +7 -0
- package/dist/react/ContextMenu.d.ts +10 -1
- package/dist/react/EditorWorkspace.d.ts +7 -0
- package/dist/react/EditorWorkspace.js +6 -3
- package/dist/react/EffectEditor.d.ts +8 -2
- package/dist/react/EffectEditorHost.d.ts +32 -28
- package/dist/react/EffectEditorHost.js +241 -1665
- package/dist/react/EffectTreeView.d.ts +3 -0
- package/dist/react/MessageDialog.d.ts +5 -0
- package/dist/react/ModuleSection.d.ts +10 -0
- package/dist/react/ModuleSection.js +4 -1
- package/dist/react/ProjectsPage.d.ts +6 -0
- package/dist/react/ProjectsPage.js +27 -11
- package/dist/react/PromptDialog.d.ts +7 -0
- package/dist/react/ScenePanel.d.ts +7 -0
- package/dist/react/ScenePanel.js +7 -5
- package/dist/react/TextureSelect.d.ts +5 -5
- package/dist/react/TimelinePanel.d.ts +13 -0
- package/dist/react/TimelinePanel.js +10 -6
- package/dist/react/TimelineTrackRow.d.ts +10 -0
- package/dist/react/TransportBar.d.ts +9 -0
- package/dist/react/ValueField.d.ts +32 -5
- package/dist/react/ValueField.js +20 -9
- package/dist/react/behaviorModules.d.ts +16 -5
- package/dist/react/behaviorModules.js +16 -1
- package/dist/react/fields.d.ts +8 -0
- package/dist/react/fields.js +3 -0
- package/dist/react/host/BottomDocks.d.ts +36 -0
- package/dist/react/host/BottomDocks.js +63 -0
- package/dist/react/host/HostDialogs.d.ts +50 -0
- package/dist/react/host/HostDialogs.js +113 -0
- package/dist/react/host/InspectorSidebar.d.ts +32 -0
- package/dist/react/host/InspectorSidebar.js +59 -0
- package/dist/react/host/SceneSidebar.d.ts +53 -0
- package/dist/react/host/SceneSidebar.js +62 -0
- package/dist/react/host/ViewportPane.d.ts +56 -0
- package/dist/react/host/ViewportPane.js +91 -0
- package/dist/react/host/hostHooks.d.ts +44 -0
- package/dist/react/host/hostHooks.js +164 -0
- package/dist/react/host/hostStyles.d.ts +21 -0
- package/dist/react/host/hostStyles.js +57 -0
- package/dist/react/index.d.ts +3 -1
- package/dist/react/modules.d.ts +17 -6
- package/dist/react/modules.js +38 -20
- package/dist/react/theme.d.ts +15 -0
- package/dist/react/theme.js +15 -0
- package/dist/session.d.ts +9 -0
- package/dist/session.js +8 -0
- package/package.json +17 -11
- package/src/core/assetTree.ts +14 -1
- package/src/core/assets.ts +25 -10
- package/src/core/binding.ts +9 -0
- package/src/core/collision.ts +4 -0
- package/src/core/colors.ts +21 -1
- package/src/core/defaultEffect.ts +4 -3
- package/src/core/editorSession.ts +1299 -0
- package/src/core/effectTree.ts +5 -0
- package/src/core/galleryPlayback.ts +2 -1
- package/src/core/history.ts +6 -0
- package/src/core/indexedDbProjectStore.ts +13 -0
- package/src/core/loadEffect.ts +1 -1
- package/src/core/material.ts +14 -4
- package/src/core/playback.ts +6 -0
- package/src/core/projectStore.ts +26 -0
- package/src/core/sceneEntries.ts +3 -0
- package/src/core/scrub.ts +2 -2
- package/src/core/shapes.ts +28 -1
- package/src/core/systems.ts +9 -2
- package/src/core/timeline.ts +40 -15
- package/src/core/values.ts +20 -4
- package/src/index.ts +5 -1
- package/src/react/AssetsPanel.tsx +7 -0
- package/src/react/ChoiceDialog.tsx +7 -0
- package/src/react/ContextMenu.tsx +10 -1
- package/src/react/EditorWorkspace.tsx +11 -5
- package/src/react/EffectEditor.tsx +7 -1
- package/src/react/EffectEditorHost.tsx +406 -2340
- package/src/react/EffectTreeView.tsx +3 -0
- package/src/react/MessageDialog.tsx +5 -0
- package/src/react/ModuleSection.tsx +11 -0
- package/src/react/ProjectsPage.tsx +38 -11
- package/src/react/PromptDialog.tsx +7 -0
- package/src/react/ScenePanel.tsx +14 -5
- package/src/react/TextureSelect.tsx +6 -6
- package/src/react/TimelinePanel.tsx +23 -6
- package/src/react/TimelineTrackRow.tsx +10 -0
- package/src/react/TransportBar.tsx +9 -0
- package/src/react/ValueField.tsx +58 -14
- package/src/react/behaviorModules.tsx +34 -32
- package/src/react/fields.tsx +12 -1
- package/src/react/host/BottomDocks.tsx +178 -0
- package/src/react/host/HostDialogs.tsx +228 -0
- package/src/react/host/InspectorSidebar.tsx +147 -0
- package/src/react/host/SceneSidebar.tsx +227 -0
- package/src/react/host/ViewportPane.tsx +300 -0
- package/src/react/host/hostHooks.ts +198 -0
- package/src/react/host/hostStyles.ts +65 -0
- package/src/react/index.ts +3 -1
- package/src/react/modules.tsx +70 -44
- package/src/react/theme.ts +15 -0
- package/src/session.ts +17 -0
|
@@ -41,14 +41,10 @@ import {CheckboxField, NumberField, Row, SelectField} from './fields.js';
|
|
|
41
41
|
import {GradientEditor} from './GradientEditor.js';
|
|
42
42
|
import {iconStyle} from './icons.js';
|
|
43
43
|
import type {TextureOption} from './modules.js';
|
|
44
|
-
import {ModuleSection} from './ModuleSection.js';
|
|
44
|
+
import {ModuleSection, type ModuleProps} from './ModuleSection.js';
|
|
45
45
|
import {theme} from './theme.js';
|
|
46
46
|
import {ValueField} from './ValueField.js';
|
|
47
47
|
|
|
48
|
-
interface ModuleProps {
|
|
49
|
-
binding: EffectBinding;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
48
|
function removeBehaviorOfType(behaviors: Behavior[], type: string): void {
|
|
53
49
|
for (let i = behaviors.length - 1; i >= 0; i--) {
|
|
54
50
|
if ((behaviors[i] as {type?: string}).type === type) {
|
|
@@ -85,6 +81,7 @@ function BehaviorModule<T extends Behavior>(props: {
|
|
|
85
81
|
);
|
|
86
82
|
}
|
|
87
83
|
|
|
84
|
+
/** The Speed over Lifetime module (Unity's Speed Modifier): scales speed by a curve over the particle's life. */
|
|
88
85
|
export function SpeedOverLifeModule({binding}: ModuleProps) {
|
|
89
86
|
return (
|
|
90
87
|
<BehaviorModule<SpeedOverLife>
|
|
@@ -92,9 +89,7 @@ export function SpeedOverLifeModule({binding}: ModuleProps) {
|
|
|
92
89
|
title="Speed over Lifetime"
|
|
93
90
|
type="SpeedOverLife"
|
|
94
91
|
create={() =>
|
|
95
|
-
new SpeedOverLife(
|
|
96
|
-
buildScalar({mode: 'curve', value: 1, min: 0, max: 1, curve: [1, 0.75, 0.5, 0]}) as never
|
|
97
|
-
)
|
|
92
|
+
new SpeedOverLife(buildScalar({mode: 'curve', value: 1, min: 0, max: 1, curve: [1, 0.75, 0.5, 0]}))
|
|
98
93
|
}
|
|
99
94
|
>
|
|
100
95
|
{(behavior) => (
|
|
@@ -102,7 +97,7 @@ export function SpeedOverLifeModule({binding}: ModuleProps) {
|
|
|
102
97
|
<CurveEditor
|
|
103
98
|
pieces={readPieces(behavior.speed)}
|
|
104
99
|
maxValue={2}
|
|
105
|
-
onChange={(pieces) => binding.apply(() => (behavior.speed = buildCurve(pieces)
|
|
100
|
+
onChange={(pieces) => binding.apply(() => (behavior.speed = buildCurve(pieces)))}
|
|
106
101
|
/>
|
|
107
102
|
</div>
|
|
108
103
|
)}
|
|
@@ -110,6 +105,7 @@ export function SpeedOverLifeModule({binding}: ModuleProps) {
|
|
|
110
105
|
);
|
|
111
106
|
}
|
|
112
107
|
|
|
108
|
+
/** The Limit Speed over Lifetime module (Unity's Limit Velocity over Lifetime): caps speed by a curve, with dampening. */
|
|
113
109
|
export function LimitSpeedOverLifeModule({binding}: ModuleProps) {
|
|
114
110
|
return (
|
|
115
111
|
<BehaviorModule<LimitSpeedOverLife>
|
|
@@ -117,10 +113,7 @@ export function LimitSpeedOverLifeModule({binding}: ModuleProps) {
|
|
|
117
113
|
title="Limit Speed over Lifetime"
|
|
118
114
|
type="LimitSpeedOverLife"
|
|
119
115
|
create={() =>
|
|
120
|
-
new LimitSpeedOverLife(
|
|
121
|
-
buildScalar({mode: 'curve', value: 1, min: 0, max: 1, curve: [4, 2, 1, 1]}) as never,
|
|
122
|
-
0.5
|
|
123
|
-
)
|
|
116
|
+
new LimitSpeedOverLife(buildScalar({mode: 'curve', value: 1, min: 0, max: 1, curve: [4, 2, 1, 1]}), 0.5)
|
|
124
117
|
}
|
|
125
118
|
>
|
|
126
119
|
{(behavior) => (
|
|
@@ -129,7 +122,7 @@ export function LimitSpeedOverLifeModule({binding}: ModuleProps) {
|
|
|
129
122
|
<CurveEditor
|
|
130
123
|
pieces={readPieces(behavior.speed)}
|
|
131
124
|
maxValue={8}
|
|
132
|
-
onChange={(pieces) => binding.apply(() => (behavior.speed = buildCurve(pieces)
|
|
125
|
+
onChange={(pieces) => binding.apply(() => (behavior.speed = buildCurve(pieces)))}
|
|
133
126
|
/>
|
|
134
127
|
</div>
|
|
135
128
|
<Row label="Dampen">
|
|
@@ -146,6 +139,7 @@ export function LimitSpeedOverLifeModule({binding}: ModuleProps) {
|
|
|
146
139
|
);
|
|
147
140
|
}
|
|
148
141
|
|
|
142
|
+
/** The Force over Lifetime module (Unity's Force over Lifetime): a constant push per axis. */
|
|
149
143
|
export function ForceOverLifeModule({binding}: ModuleProps) {
|
|
150
144
|
return (
|
|
151
145
|
<BehaviorModule<ForceOverLife>
|
|
@@ -334,6 +328,7 @@ export function AttractorModule({binding}: ModuleProps) {
|
|
|
334
328
|
);
|
|
335
329
|
}
|
|
336
330
|
|
|
331
|
+
/** The Gravity module: pulls particles towards a point. */
|
|
337
332
|
export function GravityModule({binding}: ModuleProps) {
|
|
338
333
|
return (
|
|
339
334
|
<BehaviorModule<ApplyForce>
|
|
@@ -367,6 +362,7 @@ export function GravityModule({binding}: ModuleProps) {
|
|
|
367
362
|
);
|
|
368
363
|
}
|
|
369
364
|
|
|
365
|
+
/** The Rotation over Lifetime module (Unity's Rotation over Lifetime): spin rate over the particle's life. */
|
|
370
366
|
export function RotationOverLifeModule({binding}: ModuleProps) {
|
|
371
367
|
return (
|
|
372
368
|
<BehaviorModule<RotationOverLife>
|
|
@@ -653,6 +649,7 @@ export function SequencesModule({binding, textureOptions}: ModuleProps & {textur
|
|
|
653
649
|
);
|
|
654
650
|
}
|
|
655
651
|
|
|
652
|
+
/** The Noise module (Unity's Noise): moves and turns particles by a noise field. */
|
|
656
653
|
export function NoiseModule({binding}: ModuleProps) {
|
|
657
654
|
return (
|
|
658
655
|
<BehaviorModule<Noise>
|
|
@@ -753,6 +750,7 @@ function Vector3Row(props: {
|
|
|
753
750
|
);
|
|
754
751
|
}
|
|
755
752
|
|
|
753
|
+
/** The Turbulence module: a curl noise force field. */
|
|
756
754
|
export function TurbulenceModule({binding}: ModuleProps) {
|
|
757
755
|
return (
|
|
758
756
|
<BehaviorModule<TurbulenceField>
|
|
@@ -822,6 +820,7 @@ export function TurbulenceModule({binding}: ModuleProps) {
|
|
|
822
820
|
);
|
|
823
821
|
}
|
|
824
822
|
|
|
823
|
+
/** The Randomize Direction module: tilts each particle's start direction by a random angle. */
|
|
825
824
|
export function EmitDirectionModule({binding}: ModuleProps) {
|
|
826
825
|
return (
|
|
827
826
|
<BehaviorModule<ChangeEmitDirection>
|
|
@@ -833,16 +832,17 @@ export function EmitDirectionModule({binding}: ModuleProps) {
|
|
|
833
832
|
{(behavior) => (
|
|
834
833
|
<ValueField
|
|
835
834
|
label="Angle (rad)"
|
|
836
|
-
generator={behavior.angle
|
|
835
|
+
generator={behavior.angle}
|
|
836
|
+
curve={false}
|
|
837
837
|
min={0}
|
|
838
|
-
|
|
839
|
-
onChange={(g) => binding.apply(() => (behavior.angle = g as never))}
|
|
838
|
+
onChange={(g) => binding.apply(() => (behavior.angle = g))}
|
|
840
839
|
/>
|
|
841
840
|
)}
|
|
842
841
|
</BehaviorModule>
|
|
843
842
|
);
|
|
844
843
|
}
|
|
845
844
|
|
|
845
|
+
/** The Collision module (Unity's Collision): particles bounce off or die on a ground plane. */
|
|
846
846
|
export function CollisionModule({binding}: ModuleProps) {
|
|
847
847
|
// Like Unity: Collision stores bounce only. Colliders are host-provided via
|
|
848
848
|
// setPhysicsResolver, asked when a particle is tested — the editor never invents a ground plane.
|
|
@@ -897,6 +897,7 @@ function SpeedRangeRows({binding, behavior}: {binding: EffectBinding; behavior:
|
|
|
897
897
|
);
|
|
898
898
|
}
|
|
899
899
|
|
|
900
|
+
/** The Color by Speed module (Unity's Color by Speed): a gradient over a speed range. */
|
|
900
901
|
export function ColorBySpeedModule({binding}: ModuleProps) {
|
|
901
902
|
return (
|
|
902
903
|
<BehaviorModule<ColorBySpeed>
|
|
@@ -924,6 +925,7 @@ export function ColorBySpeedModule({binding}: ModuleProps) {
|
|
|
924
925
|
);
|
|
925
926
|
}
|
|
926
927
|
|
|
928
|
+
/** The Size by Speed module (Unity's Size by Speed): size over a speed range. */
|
|
927
929
|
export function SizeBySpeedModule({binding}: ModuleProps) {
|
|
928
930
|
return (
|
|
929
931
|
<BehaviorModule<SizeBySpeed>
|
|
@@ -932,7 +934,7 @@ export function SizeBySpeedModule({binding}: ModuleProps) {
|
|
|
932
934
|
type="SizeBySpeed"
|
|
933
935
|
create={() =>
|
|
934
936
|
new SizeBySpeed(
|
|
935
|
-
buildScalar({mode: 'curve', value: 1, min: 0, max: 1, curve: [0.5, 0.8, 1.2, 1.5]})
|
|
937
|
+
buildScalar({mode: 'curve', value: 1, min: 0, max: 1, curve: [0.5, 0.8, 1.2, 1.5]}),
|
|
936
938
|
new IntervalValue(0, 5)
|
|
937
939
|
)
|
|
938
940
|
}
|
|
@@ -941,9 +943,9 @@ export function SizeBySpeedModule({binding}: ModuleProps) {
|
|
|
941
943
|
<>
|
|
942
944
|
<div style={{marginTop: 6}}>
|
|
943
945
|
<CurveEditor
|
|
944
|
-
pieces={readPieces(behavior.size
|
|
946
|
+
pieces={readPieces(behavior.size)}
|
|
945
947
|
maxValue={2}
|
|
946
|
-
onChange={(pieces) => binding.apply(() => (behavior.size = buildCurve(pieces)
|
|
948
|
+
onChange={(pieces) => binding.apply(() => (behavior.size = buildCurve(pieces)))}
|
|
947
949
|
/>
|
|
948
950
|
</div>
|
|
949
951
|
<SpeedRangeRows binding={binding} behavior={behavior} />
|
|
@@ -953,6 +955,7 @@ export function SizeBySpeedModule({binding}: ModuleProps) {
|
|
|
953
955
|
);
|
|
954
956
|
}
|
|
955
957
|
|
|
958
|
+
/** The Rotation by Speed module (Unity's Rotation by Speed): spin rate over a speed range. */
|
|
956
959
|
export function RotationBySpeedModule({binding}: ModuleProps) {
|
|
957
960
|
return (
|
|
958
961
|
<BehaviorModule<RotationBySpeed>
|
|
@@ -977,6 +980,7 @@ export function RotationBySpeedModule({binding}: ModuleProps) {
|
|
|
977
980
|
);
|
|
978
981
|
}
|
|
979
982
|
|
|
983
|
+
/** The Orbit over Lifetime module (Unity's Velocity over Lifetime orbital): turns particles about an axis. */
|
|
980
984
|
export function OrbitOverLifeModule({binding}: ModuleProps) {
|
|
981
985
|
return (
|
|
982
986
|
<BehaviorModule<OrbitOverLife>
|
|
@@ -997,6 +1001,7 @@ export function OrbitOverLifeModule({binding}: ModuleProps) {
|
|
|
997
1001
|
);
|
|
998
1002
|
}
|
|
999
1003
|
|
|
1004
|
+
/** The Width over Trail module (Unity's Trails width over trail): a trail's width along its length. */
|
|
1000
1005
|
export function WidthOverTrailModule({binding}: ModuleProps) {
|
|
1001
1006
|
return (
|
|
1002
1007
|
<BehaviorModule<WidthOverLength>
|
|
@@ -1004,18 +1009,16 @@ export function WidthOverTrailModule({binding}: ModuleProps) {
|
|
|
1004
1009
|
title="Width over Trail"
|
|
1005
1010
|
type="WidthOverLength"
|
|
1006
1011
|
create={() =>
|
|
1007
|
-
new WidthOverLength(
|
|
1008
|
-
buildScalar({mode: 'curve', value: 1, min: 0, max: 1, curve: [1, 0.75, 0.4, 0]}) as never
|
|
1009
|
-
)
|
|
1012
|
+
new WidthOverLength(buildScalar({mode: 'curve', value: 1, min: 0, max: 1, curve: [1, 0.75, 0.4, 0]}))
|
|
1010
1013
|
}
|
|
1011
1014
|
>
|
|
1012
1015
|
{(behavior) => (
|
|
1013
1016
|
<>
|
|
1014
1017
|
<div style={{marginTop: 6}}>
|
|
1015
1018
|
<CurveEditor
|
|
1016
|
-
pieces={readPieces(behavior.width
|
|
1019
|
+
pieces={readPieces(behavior.width)}
|
|
1017
1020
|
maxValue={2}
|
|
1018
|
-
onChange={(pieces) => binding.apply(() => (behavior.width = buildCurve(pieces)
|
|
1021
|
+
onChange={(pieces) => binding.apply(() => (behavior.width = buildCurve(pieces)))}
|
|
1019
1022
|
/>
|
|
1020
1023
|
</div>
|
|
1021
1024
|
{/* Off: the curve multiplies the particle size. On: it is the width in world units (files from 0.21 and earlier). */}
|
|
@@ -1033,10 +1036,11 @@ export function WidthOverTrailModule({binding}: ModuleProps) {
|
|
|
1033
1036
|
|
|
1034
1037
|
/** How many of the first frames a start-frame generator picks from: its exclusive upper bound. */
|
|
1035
1038
|
function startFrameCount(startFrame: FrameOverLife['startFrame']): number {
|
|
1036
|
-
const state = readScalar(startFrame
|
|
1039
|
+
const state = readScalar(startFrame);
|
|
1037
1040
|
return Math.round(state.mode === 'random' ? state.max : state.value);
|
|
1038
1041
|
}
|
|
1039
1042
|
|
|
1043
|
+
/** The Texture Sheet Animation module (Unity's Texture Sheet Animation): plays the sheet's frames by life or by frame rate. */
|
|
1040
1044
|
export function TextureSheetModule({binding}: ModuleProps) {
|
|
1041
1045
|
const system = binding.system;
|
|
1042
1046
|
const frameBehavior = findBehavior<FrameOverLife>(system.behaviors, 'FrameOverLife');
|
|
@@ -1045,7 +1049,7 @@ export function TextureSheetModule({binding}: ModuleProps) {
|
|
|
1045
1049
|
const u = Math.max(1, system.uTileCount);
|
|
1046
1050
|
const v = Math.max(1, system.vTileCount);
|
|
1047
1051
|
// Start tile → cell (row-major, top-left origin — matches the shader's tile UV mapping).
|
|
1048
|
-
const startIdx = Math.min(Math.max(0, Math.round(readScalar(system.startTileIndex
|
|
1052
|
+
const startIdx = Math.min(Math.max(0, Math.round(readScalar(system.startTileIndex).value)), tiles - 1);
|
|
1049
1053
|
const startCol = startIdx % u;
|
|
1050
1054
|
const startRow = Math.floor(startIdx / u);
|
|
1051
1055
|
return (
|
|
@@ -1134,7 +1138,7 @@ export function TextureSheetModule({binding}: ModuleProps) {
|
|
|
1134
1138
|
</Row>
|
|
1135
1139
|
<Row label="Start tile">
|
|
1136
1140
|
<NumberField
|
|
1137
|
-
value={readScalar(system.startTileIndex
|
|
1141
|
+
value={readScalar(system.startTileIndex).value}
|
|
1138
1142
|
min={0}
|
|
1139
1143
|
step={1}
|
|
1140
1144
|
onChange={(v) => binding.apply((s) => (s.startTileIndex = new ConstantValue(Math.round(v))))}
|
|
@@ -1158,7 +1162,7 @@ export function TextureSheetModule({binding}: ModuleProps) {
|
|
|
1158
1162
|
min: 0,
|
|
1159
1163
|
max: tiles - 1,
|
|
1160
1164
|
curve: [0, (tiles - 1) / 3, (2 * (tiles - 1)) / 3, tiles - 1],
|
|
1161
|
-
})
|
|
1165
|
+
})
|
|
1162
1166
|
)
|
|
1163
1167
|
);
|
|
1164
1168
|
}
|
|
@@ -1264,9 +1268,7 @@ export function TextureSheetModule({binding}: ModuleProps) {
|
|
|
1264
1268
|
<CurveEditor
|
|
1265
1269
|
pieces={readPieces(frameBehavior.frame)}
|
|
1266
1270
|
maxValue={Math.max(1, tiles - 1)}
|
|
1267
|
-
onChange={(pieces) =>
|
|
1268
|
-
binding.apply(() => (frameBehavior.frame = buildCurve(pieces) as never))
|
|
1269
|
-
}
|
|
1271
|
+
onChange={(pieces) => binding.apply(() => (frameBehavior.frame = buildCurve(pieces)))}
|
|
1270
1272
|
/>
|
|
1271
1273
|
</div>
|
|
1272
1274
|
</>
|
package/src/react/fields.tsx
CHANGED
|
@@ -2,6 +2,7 @@ import React, {useEffect, useState} from 'react';
|
|
|
2
2
|
import {InspectorLabel} from './InspectorLabel.js';
|
|
3
3
|
import {inputStyle, rowStyle} from './theme.js';
|
|
4
4
|
|
|
5
|
+
/** An inspector row: a label, with an optional hint, and its controls. */
|
|
5
6
|
export function Row(props: {label: string; hint?: string; hintKey?: string; children: React.ReactNode}) {
|
|
6
7
|
return (
|
|
7
8
|
<div style={rowStyle}>
|
|
@@ -53,6 +54,7 @@ export function NumberField(props: {
|
|
|
53
54
|
);
|
|
54
55
|
}
|
|
55
56
|
|
|
57
|
+
/** A checkbox bound to a boolean. */
|
|
56
58
|
export function CheckboxField(props: {value: boolean; onChange: (value: boolean) => void}) {
|
|
57
59
|
return (
|
|
58
60
|
<input
|
|
@@ -65,9 +67,18 @@ export function CheckboxField(props: {value: boolean; onChange: (value: boolean)
|
|
|
65
67
|
);
|
|
66
68
|
}
|
|
67
69
|
|
|
70
|
+
/** A select bound to a value, from a list of options. */
|
|
68
71
|
export function SelectField<T extends string | number>(props: {
|
|
72
|
+
/** The selected value. */
|
|
69
73
|
value: T;
|
|
70
|
-
options
|
|
74
|
+
/** The options, in menu order. */
|
|
75
|
+
options: Array<{
|
|
76
|
+
/** The option's value. */
|
|
77
|
+
value: T;
|
|
78
|
+
/** Its text. */
|
|
79
|
+
label: string;
|
|
80
|
+
}>;
|
|
81
|
+
/** Receives the picked value, a number when the values are numbers. */
|
|
71
82
|
onChange: (value: T) => void;
|
|
72
83
|
}) {
|
|
73
84
|
const numeric = typeof props.value === 'number';
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import {ChevronDownIcon, ChevronUpIcon, MapPinIcon, XMarkIcon} from '@heroicons/react/24/solid';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import {AssetsPanel, type AssetsPanelProps} from '../AssetsPanel.js';
|
|
4
|
+
import {iconStyle} from '../icons.js';
|
|
5
|
+
import {buttonStyle, theme} from '../theme.js';
|
|
6
|
+
import {
|
|
7
|
+
activeSquareButtonStyle,
|
|
8
|
+
dockIconButtonStyle,
|
|
9
|
+
dockTitleStyle,
|
|
10
|
+
DRAWER_HEIGHT,
|
|
11
|
+
TIMELINE_HEIGHT,
|
|
12
|
+
} from './hostStyles.js';
|
|
13
|
+
|
|
14
|
+
/** Props of {@link AssetsDrawer}. */
|
|
15
|
+
export interface AssetsDrawerProps {
|
|
16
|
+
/** The drawer is shown. */
|
|
17
|
+
open: boolean;
|
|
18
|
+
/** The drawer is docked above the timeline instead of floating over the viewport. */
|
|
19
|
+
pinned: boolean;
|
|
20
|
+
/** Pin or unpin it (it stays open). */
|
|
21
|
+
onTogglePin(): void;
|
|
22
|
+
/** Close and unpin it. */
|
|
23
|
+
onClose(): void;
|
|
24
|
+
/** The Assets browser inside. */
|
|
25
|
+
panel: AssetsPanelProps;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** The Assets browser, floating over the viewport or pinned above the timeline. */
|
|
29
|
+
export function AssetsDrawer(props: AssetsDrawerProps): React.ReactElement {
|
|
30
|
+
const {open, pinned} = props;
|
|
31
|
+
return (
|
|
32
|
+
<div
|
|
33
|
+
aria-hidden={!open && !pinned}
|
|
34
|
+
style={{
|
|
35
|
+
height: DRAWER_HEIGHT,
|
|
36
|
+
display: 'flex',
|
|
37
|
+
flexDirection: 'column',
|
|
38
|
+
background: theme.panelBg,
|
|
39
|
+
borderTop: `1px solid ${theme.border}`,
|
|
40
|
+
...(pinned
|
|
41
|
+
? {flexShrink: 0}
|
|
42
|
+
: {
|
|
43
|
+
position: 'absolute',
|
|
44
|
+
left: 0,
|
|
45
|
+
right: 0,
|
|
46
|
+
bottom: 0,
|
|
47
|
+
zIndex: 20,
|
|
48
|
+
background: '#0a0f20',
|
|
49
|
+
boxShadow: '0 -10px 30px rgba(0, 0, 0, 0.5)',
|
|
50
|
+
transform: open ? 'none' : 'translateY(100%)',
|
|
51
|
+
opacity: open ? 1 : 0,
|
|
52
|
+
pointerEvents: open ? 'auto' : 'none',
|
|
53
|
+
transition: 'transform 0.18s ease, opacity 0.18s ease',
|
|
54
|
+
}),
|
|
55
|
+
}}
|
|
56
|
+
>
|
|
57
|
+
<div
|
|
58
|
+
style={{
|
|
59
|
+
display: 'flex',
|
|
60
|
+
alignItems: 'center',
|
|
61
|
+
gap: 6,
|
|
62
|
+
padding: '4px 6px 4px 12px',
|
|
63
|
+
borderBottom: `1px solid ${theme.border}`,
|
|
64
|
+
}}
|
|
65
|
+
>
|
|
66
|
+
<span style={dockTitleStyle}>Assets</span>
|
|
67
|
+
<span style={{fontSize: 11, color: theme.textDim}}>Ctrl+Space</span>
|
|
68
|
+
<span style={{flex: 1}} />
|
|
69
|
+
<button
|
|
70
|
+
type="button"
|
|
71
|
+
className="qe-hover"
|
|
72
|
+
title={pinned ? 'Unpin — float over the viewport again' : 'Pin — keep it docked above the timeline'}
|
|
73
|
+
style={{...dockIconButtonStyle, ...(pinned ? activeSquareButtonStyle : {})}}
|
|
74
|
+
onClick={props.onTogglePin}
|
|
75
|
+
>
|
|
76
|
+
<MapPinIcon style={iconStyle(13)} />
|
|
77
|
+
</button>
|
|
78
|
+
<button
|
|
79
|
+
type="button"
|
|
80
|
+
className="qe-hover"
|
|
81
|
+
title="Close (Esc)"
|
|
82
|
+
style={dockIconButtonStyle}
|
|
83
|
+
onClick={props.onClose}
|
|
84
|
+
>
|
|
85
|
+
<XMarkIcon style={iconStyle(13)} />
|
|
86
|
+
</button>
|
|
87
|
+
</div>
|
|
88
|
+
<AssetsPanel {...props.panel} />
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Props of {@link TimelineDock}. */
|
|
94
|
+
export interface TimelineDockProps {
|
|
95
|
+
/** The timeline is collapsed to its header. */
|
|
96
|
+
collapsed: boolean;
|
|
97
|
+
/** Collapse or expand it. */
|
|
98
|
+
onToggleCollapsed(): void;
|
|
99
|
+
/** Name of the focused effect, if any. */
|
|
100
|
+
effectName?: string;
|
|
101
|
+
/** Assets in the project, counted on the Assets button. */
|
|
102
|
+
assetCount: number;
|
|
103
|
+
/** The Assets button shows as switched on. */
|
|
104
|
+
assetsActive: boolean;
|
|
105
|
+
/** The Assets button. */
|
|
106
|
+
onToggleAssets(): void;
|
|
107
|
+
/** The timeline, or `null` when nothing is focused. */
|
|
108
|
+
children: React.ReactNode;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** The bottom dock: the focused effect's timeline, and the Assets button. */
|
|
112
|
+
export function TimelineDock(props: TimelineDockProps): React.ReactElement {
|
|
113
|
+
const {collapsed} = props;
|
|
114
|
+
return (
|
|
115
|
+
<div
|
|
116
|
+
style={{
|
|
117
|
+
flexShrink: 0,
|
|
118
|
+
borderTop: `1px solid ${theme.border}`,
|
|
119
|
+
background: theme.panelBg,
|
|
120
|
+
display: 'flex',
|
|
121
|
+
flexDirection: 'column',
|
|
122
|
+
}}
|
|
123
|
+
>
|
|
124
|
+
<div style={{display: 'flex', alignItems: 'center', gap: 6, padding: '3px 6px'}}>
|
|
125
|
+
<button
|
|
126
|
+
type="button"
|
|
127
|
+
className="qe-hover-bg"
|
|
128
|
+
title={collapsed ? 'Expand timeline' : 'Collapse timeline'}
|
|
129
|
+
style={{...dockIconButtonStyle, border: 'none', background: 'transparent'}}
|
|
130
|
+
onClick={props.onToggleCollapsed}
|
|
131
|
+
>
|
|
132
|
+
{collapsed ? <ChevronUpIcon style={iconStyle(13)} /> : <ChevronDownIcon style={iconStyle(13)} />}
|
|
133
|
+
</button>
|
|
134
|
+
<span style={dockTitleStyle}>Timeline</span>
|
|
135
|
+
{props.effectName !== undefined && (
|
|
136
|
+
<span
|
|
137
|
+
style={{
|
|
138
|
+
fontSize: 12,
|
|
139
|
+
color: theme.textDim,
|
|
140
|
+
overflow: 'hidden',
|
|
141
|
+
textOverflow: 'ellipsis',
|
|
142
|
+
whiteSpace: 'nowrap',
|
|
143
|
+
}}
|
|
144
|
+
>
|
|
145
|
+
{props.effectName}
|
|
146
|
+
</span>
|
|
147
|
+
)}
|
|
148
|
+
<span style={{flex: 1}} />
|
|
149
|
+
<button
|
|
150
|
+
type="button"
|
|
151
|
+
className="qe-hover"
|
|
152
|
+
title="Assets (Ctrl+Space)"
|
|
153
|
+
style={{
|
|
154
|
+
...buttonStyle,
|
|
155
|
+
padding: '3px 10px',
|
|
156
|
+
...(props.assetsActive ? activeSquareButtonStyle : {}),
|
|
157
|
+
}}
|
|
158
|
+
onClick={props.onToggleAssets}
|
|
159
|
+
>
|
|
160
|
+
Assets{props.assetCount > 0 ? ` · ${props.assetCount}` : ''}
|
|
161
|
+
</button>
|
|
162
|
+
</div>
|
|
163
|
+
<div
|
|
164
|
+
style={{
|
|
165
|
+
height: TIMELINE_HEIGHT,
|
|
166
|
+
display: collapsed ? 'none' : 'flex',
|
|
167
|
+
borderTop: `1px solid ${theme.border}`,
|
|
168
|
+
}}
|
|
169
|
+
>
|
|
170
|
+
{props.children ?? (
|
|
171
|
+
<div style={{padding: '14px', fontSize: 12, color: theme.textDim}}>
|
|
172
|
+
Select an effect in the Scene to see its timeline.
|
|
173
|
+
</div>
|
|
174
|
+
)}
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
177
|
+
);
|
|
178
|
+
}
|