babylon.quarks-editor 0.26.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 +134 -0
- package/README.md +69 -9
- package/dist/core/assetDragSession.d.ts +1 -1
- package/dist/core/assetTree.d.ts +6 -1
- package/dist/core/assets.d.ts +7 -0
- package/dist/core/assets.js +11 -7
- package/dist/core/binding.d.ts +11 -2
- package/dist/core/binding.js +9 -1
- 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 +5 -4
- package/dist/core/defaultEffect.js +4 -4
- package/dist/core/editorScene.d.ts +1 -1
- package/dist/core/editorSession.d.ts +229 -0
- package/dist/core/editorSession.js +983 -0
- package/dist/core/effectTree.d.ts +6 -1
- package/dist/core/effectTree.js +13 -88
- package/dist/core/emitterShapeWireframe.d.ts +2 -2
- package/dist/core/emitterShapeWireframe.js +45 -13
- package/dist/core/galleryDropMarker.d.ts +2 -2
- package/dist/core/galleryDropMarker.js +6 -6
- package/dist/core/galleryLayout.d.ts +3 -3
- package/dist/core/galleryLayout.js +1 -1
- package/dist/core/galleryPlayback.d.ts +1 -1
- package/dist/core/galleryPlayback.js +2 -1
- package/dist/core/galleryScene.d.ts +4 -4
- package/dist/core/galleryScene.js +3 -3
- package/dist/core/gallerySelectionMarker.d.ts +4 -4
- package/dist/core/gallerySelectionMarker.js +5 -5
- package/dist/core/groups.d.ts +1 -1
- package/dist/core/groups.js +1 -1
- package/dist/core/history.d.ts +7 -1
- package/dist/core/history.js +4 -0
- package/dist/core/indexedDbProjectStore.d.ts +14 -1
- package/dist/core/indexedDbProjectStore.js +13 -0
- package/dist/core/lineShaders.d.ts +2 -2
- package/dist/core/lineShaders.js +2 -2
- package/dist/core/loadEffect.d.ts +2 -2
- package/dist/core/loadEffect.js +1 -1
- package/dist/core/material.d.ts +12 -2
- package/dist/core/pickAssetFiles.js +1 -1
- 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 +7 -4
- package/dist/core/sceneEntries.js +2 -2
- package/dist/core/scrub.d.ts +2 -2
- package/dist/core/scrub.js +2 -2
- package/dist/core/shapes.d.ts +25 -1
- package/dist/core/shapes.js +5 -0
- package/dist/core/systems.d.ts +4 -2
- package/dist/core/systems.js +3 -1
- package/dist/core/textureExport.d.ts +1 -1
- package/dist/core/timeline.d.ts +25 -5
- package/dist/core/transport.d.ts +3 -3
- package/dist/core/transport.js +2 -2
- package/dist/core/values.d.ts +21 -4
- package/dist/core/values.js +5 -0
- package/dist/core/viewportPicker.d.ts +4 -4
- package/dist/core/viewportPicker.js +8 -7
- package/dist/index.d.ts +25 -21
- package/dist/index.js +12 -12
- package/dist/react/AssetsPanel.d.ts +9 -2
- package/dist/react/AssetsPanel.js +6 -6
- package/dist/react/ChoiceDialog.d.ts +7 -0
- package/dist/react/ChoiceDialog.js +1 -1
- package/dist/react/ContextMenu.d.ts +10 -1
- package/dist/react/ContextMenu.js +1 -1
- package/dist/react/CurveEditor.d.ts +1 -1
- package/dist/react/CurveEditor.js +2 -2
- package/dist/react/EditorWorkspace.d.ts +9 -2
- package/dist/react/EditorWorkspace.js +10 -7
- package/dist/react/EffectEditor.d.ts +11 -5
- package/dist/react/EffectEditor.js +7 -7
- package/dist/react/EffectEditorHost.d.ts +33 -29
- package/dist/react/EffectEditorHost.js +247 -1674
- package/dist/react/EffectTreeView.d.ts +4 -1
- package/dist/react/EffectTreeView.js +3 -3
- package/dist/react/GradientEditor.d.ts +1 -1
- package/dist/react/GradientEditor.js +3 -3
- package/dist/react/InspectorInfoIcon.js +3 -3
- package/dist/react/InspectorLabel.js +3 -3
- package/dist/react/MessageDialog.d.ts +5 -0
- package/dist/react/MessageDialog.js +1 -1
- package/dist/react/ModuleSection.d.ts +10 -0
- package/dist/react/ModuleSection.js +8 -5
- package/dist/react/ObjectModule.d.ts +1 -1
- package/dist/react/ObjectModule.js +4 -4
- package/dist/react/ProjectsPage.d.ts +7 -1
- package/dist/react/ProjectsPage.js +33 -17
- package/dist/react/PromptDialog.d.ts +7 -0
- package/dist/react/PromptDialog.js +1 -1
- package/dist/react/ScenePanel.d.ts +9 -2
- package/dist/react/ScenePanel.js +12 -10
- package/dist/react/SubEmittersModule.d.ts +1 -1
- package/dist/react/SubEmittersModule.js +5 -5
- package/dist/react/TextureSelect.d.ts +24 -0
- package/dist/react/TextureSelect.js +74 -0
- package/dist/react/TextureUVField.d.ts +14 -0
- package/dist/react/TextureUVField.js +23 -0
- package/dist/react/TimelinePanel.d.ts +20 -7
- package/dist/react/TimelinePanel.js +21 -15
- package/dist/react/TimelineTrackRow.d.ts +13 -3
- package/dist/react/TimelineTrackRow.js +6 -6
- package/dist/react/TransportBar.d.ts +13 -4
- package/dist/react/TransportBar.js +3 -3
- package/dist/react/ValueField.d.ts +32 -5
- package/dist/react/ValueField.js +22 -11
- package/dist/react/behaviorModules.d.ts +19 -6
- package/dist/react/behaviorModules.js +68 -17
- package/dist/react/fields.d.ts +9 -0
- package/dist/react/fields.js +6 -3
- 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 +42 -38
- package/dist/react/index.js +25 -23
- package/dist/react/inspectorHints.js +31 -0
- package/dist/react/modules.d.ts +19 -6
- package/dist/react/modules.js +134 -117
- 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 +18 -11
- package/src/core/assetDragSession.ts +1 -1
- package/src/core/assetTree.ts +15 -2
- package/src/core/assets.ts +25 -10
- package/src/core/binding.ts +12 -3
- package/src/core/collision.ts +4 -0
- package/src/core/colors.ts +21 -1
- package/src/core/defaultEffect.ts +8 -7
- package/src/core/editorScene.ts +1 -1
- package/src/core/editorSession.ts +1299 -0
- package/src/core/effectTree.ts +18 -101
- package/src/core/emitterShapeWireframe.ts +48 -16
- package/src/core/galleryDropMarker.ts +8 -8
- package/src/core/galleryLayout.ts +4 -4
- package/src/core/galleryPlayback.ts +3 -2
- package/src/core/galleryScene.ts +5 -5
- package/src/core/gallerySelectionMarker.ts +7 -7
- package/src/core/groups.ts +1 -1
- package/src/core/history.ts +7 -1
- package/src/core/indexedDbProjectStore.ts +14 -1
- package/src/core/lineShaders.ts +2 -2
- package/src/core/loadEffect.ts +4 -4
- package/src/core/material.ts +14 -4
- package/src/core/pickAssetFiles.ts +2 -2
- package/src/core/playback.ts +6 -0
- package/src/core/projectStore.ts +26 -0
- package/src/core/sceneEntries.ts +8 -5
- package/src/core/scrub.ts +3 -3
- package/src/core/shapes.ts +29 -2
- package/src/core/systems.ts +11 -4
- package/src/core/textureExport.ts +1 -1
- package/src/core/timeline.ts +43 -18
- package/src/core/transport.ts +4 -4
- package/src/core/values.ts +20 -4
- package/src/core/viewportPicker.ts +12 -11
- package/src/index.ts +25 -21
- package/src/react/AssetsPanel.tsx +13 -6
- package/src/react/ChoiceDialog.tsx +8 -1
- package/src/react/ContextMenu.tsx +11 -2
- package/src/react/CurveEditor.tsx +2 -2
- package/src/react/EditorWorkspace.tsx +16 -10
- package/src/react/EffectEditor.tsx +17 -9
- package/src/react/EffectEditorHost.tsx +415 -2354
- package/src/react/EffectTreeView.tsx +7 -4
- package/src/react/GradientEditor.tsx +3 -3
- package/src/react/InspectorInfoIcon.tsx +3 -3
- package/src/react/InspectorLabel.tsx +3 -3
- package/src/react/MessageDialog.tsx +6 -1
- package/src/react/ModuleSection.tsx +15 -4
- package/src/react/ObjectModule.tsx +5 -5
- package/src/react/ProjectsPage.tsx +44 -17
- package/src/react/PromptDialog.tsx +8 -1
- package/src/react/ScenePanel.tsx +21 -12
- package/src/react/SubEmittersModule.tsx +6 -6
- package/src/react/TextureSelect.tsx +118 -0
- package/src/react/TextureUVField.tsx +62 -0
- package/src/react/TimelinePanel.tsx +40 -21
- package/src/react/TimelineTrackRow.tsx +19 -9
- package/src/react/TransportBar.tsx +16 -7
- package/src/react/ValueField.tsx +60 -16
- package/src/react/behaviorModules.tsx +240 -55
- package/src/react/fields.tsx +22 -4
- 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 +43 -38
- package/src/react/inspectorHints.ts +40 -0
- package/src/react/modules.tsx +440 -170
- package/src/react/theme.ts +15 -0
- package/src/session.ts +17 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import type {TransformNode} from '@babylonjs/core/Meshes/transformNode.js';
|
|
2
|
+
import {ArrowLeftIcon} from '@heroicons/react/24/solid';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import type {PrefabInfo} from '../../core/editorSession.js';
|
|
5
|
+
import type {SceneEntry} from '../../core/sceneEntries.js';
|
|
6
|
+
import {EffectTreeView} from '../EffectTreeView.js';
|
|
7
|
+
import {iconStyle} from '../icons.js';
|
|
8
|
+
import {ScenePanel} from '../ScenePanel.js';
|
|
9
|
+
import {buttonStyle, theme} from '../theme.js';
|
|
10
|
+
import {dockIconButtonStyle, saveButtonStyle, SCENE_PANEL_WIDTH} from './hostStyles.js';
|
|
11
|
+
|
|
12
|
+
/** Props of {@link SceneSidebar}. */
|
|
13
|
+
export interface SceneSidebarProps {
|
|
14
|
+
/** The open project, for its header; none in memory-only mode. */
|
|
15
|
+
projectName?: string;
|
|
16
|
+
/** Shows the back button in the project header. */
|
|
17
|
+
canExitProject: boolean;
|
|
18
|
+
/** The scene has unsaved changes. */
|
|
19
|
+
sceneDirty: boolean;
|
|
20
|
+
/** The project's scene is still loading. */
|
|
21
|
+
projectLoading: boolean;
|
|
22
|
+
/** The open prefab, if any. */
|
|
23
|
+
prefabInfo: PrefabInfo | null;
|
|
24
|
+
/** The open prefab has unsaved edits. */
|
|
25
|
+
prefabDirty: boolean;
|
|
26
|
+
/** The scene's entries. */
|
|
27
|
+
entries: SceneEntry[];
|
|
28
|
+
/** The focused entry's root. */
|
|
29
|
+
focusedRoot: TransformNode | null;
|
|
30
|
+
/** Shown entries, by root `uniqueId`. */
|
|
31
|
+
visibleRootIds: ReadonlySet<number>;
|
|
32
|
+
/** The entry shown alone, if any. */
|
|
33
|
+
soloRootId: number | null;
|
|
34
|
+
/** The node selected in the focused effect. */
|
|
35
|
+
selectedNode: TransformNode | null;
|
|
36
|
+
/** Back to the projects list. */
|
|
37
|
+
onExitProject(): void;
|
|
38
|
+
/** Save (the scene, or the open prefab). */
|
|
39
|
+
onSave(): void;
|
|
40
|
+
/** Back from the prefab to the scene. */
|
|
41
|
+
onExitPrefab(): void;
|
|
42
|
+
/** Save the open prefab. */
|
|
43
|
+
onSavePrefab(): void;
|
|
44
|
+
/** Focus an entry. */
|
|
45
|
+
onFocus(entry: SceneEntry): void;
|
|
46
|
+
/** Show or hide an entry. */
|
|
47
|
+
onToggleVisible(entry: SceneEntry): void;
|
|
48
|
+
/** Show an entry alone. */
|
|
49
|
+
onToggleSolo(entry: SceneEntry): void;
|
|
50
|
+
/** Save an entry as a new asset. */
|
|
51
|
+
onSaveAsAsset(entry: SceneEntry): void;
|
|
52
|
+
/** Remove an entry. */
|
|
53
|
+
onDelete(entry: SceneEntry): void;
|
|
54
|
+
/** Add a default effect. */
|
|
55
|
+
onCreateDefault(): void;
|
|
56
|
+
/** Select a node, focusing its entry first when needed. */
|
|
57
|
+
onSelectNode(entry: SceneEntry | null, node: TransformNode): void;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** The editor's left column: the project header, then the Scene list or the open prefab. */
|
|
61
|
+
export function SceneSidebar(props: SceneSidebarProps): React.ReactElement {
|
|
62
|
+
const {prefabInfo, prefabDirty, entries} = props;
|
|
63
|
+
return (
|
|
64
|
+
<aside
|
|
65
|
+
style={{
|
|
66
|
+
width: SCENE_PANEL_WIDTH,
|
|
67
|
+
flexShrink: 0,
|
|
68
|
+
borderRight: `1px solid ${theme.border}`,
|
|
69
|
+
background: theme.panelBg,
|
|
70
|
+
display: 'flex',
|
|
71
|
+
flexDirection: 'column',
|
|
72
|
+
minHeight: 0,
|
|
73
|
+
}}
|
|
74
|
+
>
|
|
75
|
+
{props.projectName !== undefined && (
|
|
76
|
+
<div
|
|
77
|
+
style={{
|
|
78
|
+
display: 'flex',
|
|
79
|
+
alignItems: 'center',
|
|
80
|
+
gap: 6,
|
|
81
|
+
padding: '8px 10px',
|
|
82
|
+
borderBottom: `1px solid ${theme.border}`,
|
|
83
|
+
}}
|
|
84
|
+
>
|
|
85
|
+
{props.canExitProject && (
|
|
86
|
+
<button
|
|
87
|
+
type="button"
|
|
88
|
+
className="qe-hover"
|
|
89
|
+
title="Back to projects"
|
|
90
|
+
style={{...dockIconButtonStyle, width: 26, height: 26}}
|
|
91
|
+
onClick={props.onExitProject}
|
|
92
|
+
>
|
|
93
|
+
<ArrowLeftIcon style={iconStyle(13)} />
|
|
94
|
+
</button>
|
|
95
|
+
)}
|
|
96
|
+
<span
|
|
97
|
+
title={props.projectName}
|
|
98
|
+
style={{
|
|
99
|
+
flex: 1,
|
|
100
|
+
minWidth: 0,
|
|
101
|
+
fontSize: 13,
|
|
102
|
+
fontWeight: 600,
|
|
103
|
+
overflow: 'hidden',
|
|
104
|
+
textOverflow: 'ellipsis',
|
|
105
|
+
whiteSpace: 'nowrap',
|
|
106
|
+
}}
|
|
107
|
+
>
|
|
108
|
+
{props.projectName}
|
|
109
|
+
{props.sceneDirty ? ' *' : ''}
|
|
110
|
+
</span>
|
|
111
|
+
<button
|
|
112
|
+
type="button"
|
|
113
|
+
className="qe-hover"
|
|
114
|
+
title="Save the scene (Ctrl+S) — assets are not changed"
|
|
115
|
+
disabled={props.projectLoading}
|
|
116
|
+
style={saveButtonStyle(props.sceneDirty, '4px 12px')}
|
|
117
|
+
onClick={props.onSave}
|
|
118
|
+
>
|
|
119
|
+
Save
|
|
120
|
+
</button>
|
|
121
|
+
</div>
|
|
122
|
+
)}
|
|
123
|
+
<div style={{padding: '12px 14px 10px', borderBottom: `1px solid ${theme.border}`}}>
|
|
124
|
+
<div style={{fontSize: 14, fontWeight: 600}}>
|
|
125
|
+
{prefabInfo ? 'Prefab' : `Scene${entries.length > 0 ? ` · ${entries.length}` : ''}`}
|
|
126
|
+
</div>
|
|
127
|
+
<div style={{fontSize: 11, color: theme.textDim, marginTop: 4, lineHeight: 1.4}}>
|
|
128
|
+
{prefabInfo
|
|
129
|
+
? 'The scene is set aside while you edit this asset'
|
|
130
|
+
: 'Effects in the viewport · click to edit · eye shows/hides'}
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
<div
|
|
134
|
+
style={{
|
|
135
|
+
flex: 1,
|
|
136
|
+
minHeight: 0,
|
|
137
|
+
overflow: 'hidden',
|
|
138
|
+
padding: '8px 12px 14px',
|
|
139
|
+
display: 'flex',
|
|
140
|
+
flexDirection: 'column',
|
|
141
|
+
}}
|
|
142
|
+
>
|
|
143
|
+
{prefabInfo ? (
|
|
144
|
+
<div style={{display: 'flex', flexDirection: 'column', gap: 10, flex: 1, minHeight: 0}}>
|
|
145
|
+
<div
|
|
146
|
+
style={{
|
|
147
|
+
display: 'flex',
|
|
148
|
+
flexDirection: 'column',
|
|
149
|
+
gap: 10,
|
|
150
|
+
padding: 12,
|
|
151
|
+
border: `1px solid ${theme.borderActive}`,
|
|
152
|
+
borderRadius: 8,
|
|
153
|
+
background: 'rgba(120, 165, 255, 0.08)',
|
|
154
|
+
fontSize: 12,
|
|
155
|
+
color: theme.textDim,
|
|
156
|
+
lineHeight: 1.5,
|
|
157
|
+
}}
|
|
158
|
+
>
|
|
159
|
+
<div style={{fontSize: 13, fontWeight: 600, color: theme.text}}>
|
|
160
|
+
{prefabInfo.name}
|
|
161
|
+
{prefabDirty ? ' *' : ''}
|
|
162
|
+
</div>
|
|
163
|
+
<div style={{wordBreak: 'break-all'}}>{prefabInfo.path}</div>
|
|
164
|
+
<div>
|
|
165
|
+
Save (Ctrl+S) overwrites this asset. Effects already placed in the scene keep their own
|
|
166
|
+
copy and do not change.
|
|
167
|
+
</div>
|
|
168
|
+
<div style={{display: 'flex', gap: 6}}>
|
|
169
|
+
<button
|
|
170
|
+
type="button"
|
|
171
|
+
className="qe-hover"
|
|
172
|
+
style={buttonStyle}
|
|
173
|
+
onClick={props.onExitPrefab}
|
|
174
|
+
>
|
|
175
|
+
← Scene
|
|
176
|
+
</button>
|
|
177
|
+
<button
|
|
178
|
+
type="button"
|
|
179
|
+
className="qe-hover"
|
|
180
|
+
style={buttonStyle}
|
|
181
|
+
onClick={props.onSavePrefab}
|
|
182
|
+
>
|
|
183
|
+
Save prefab
|
|
184
|
+
</button>
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
{entries[0] && (
|
|
188
|
+
<div
|
|
189
|
+
style={{
|
|
190
|
+
flex: 1,
|
|
191
|
+
minHeight: 0,
|
|
192
|
+
overflowY: 'auto',
|
|
193
|
+
border: `1px solid ${theme.border}`,
|
|
194
|
+
borderRadius: 8,
|
|
195
|
+
background: theme.sectionBg,
|
|
196
|
+
padding: '4px 0',
|
|
197
|
+
}}
|
|
198
|
+
>
|
|
199
|
+
<EffectTreeView
|
|
200
|
+
includeRoot
|
|
201
|
+
root={entries[0].root}
|
|
202
|
+
selectedNode={props.selectedNode}
|
|
203
|
+
onSelect={(node) => props.onSelectNode(null, node)}
|
|
204
|
+
/>
|
|
205
|
+
</div>
|
|
206
|
+
)}
|
|
207
|
+
</div>
|
|
208
|
+
) : (
|
|
209
|
+
<ScenePanel
|
|
210
|
+
entries={entries}
|
|
211
|
+
focusedRoot={props.focusedRoot}
|
|
212
|
+
visibleRootIds={props.visibleRootIds}
|
|
213
|
+
onFocus={props.onFocus}
|
|
214
|
+
onToggleVisible={props.onToggleVisible}
|
|
215
|
+
soloRootId={props.soloRootId}
|
|
216
|
+
onToggleSolo={props.onToggleSolo}
|
|
217
|
+
onSaveAsAsset={props.onSaveAsAsset}
|
|
218
|
+
onDelete={props.onDelete}
|
|
219
|
+
onCreateDefault={props.onCreateDefault}
|
|
220
|
+
selectedNode={props.selectedNode}
|
|
221
|
+
onSelectNode={props.onSelectNode}
|
|
222
|
+
/>
|
|
223
|
+
)}
|
|
224
|
+
</div>
|
|
225
|
+
</aside>
|
|
226
|
+
);
|
|
227
|
+
}
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ArrowLeftIcon,
|
|
3
|
+
ArrowPathIcon,
|
|
4
|
+
ArrowsPointingOutIcon,
|
|
5
|
+
ArrowUturnLeftIcon,
|
|
6
|
+
ArrowUturnRightIcon,
|
|
7
|
+
CubeTransparentIcon,
|
|
8
|
+
CursorArrowRaysIcon,
|
|
9
|
+
Square2StackIcon,
|
|
10
|
+
} from '@heroicons/react/24/solid';
|
|
11
|
+
import React from 'react';
|
|
12
|
+
import type {AssetLoadProgress} from '../../core/assets.js';
|
|
13
|
+
import type {PrefabInfo} from '../../core/editorSession.js';
|
|
14
|
+
import {iconStyle, type HeroIcon} from '../icons.js';
|
|
15
|
+
import {buttonStyle, theme} from '../theme.js';
|
|
16
|
+
import {activeSquareButtonStyle, floatingPanelStyle, saveButtonStyle, squareButtonStyle} from './hostStyles.js';
|
|
17
|
+
|
|
18
|
+
/** What the stage gizmo does: nothing, or move, rotate or scale the selected node. */
|
|
19
|
+
export type GizmoMode = 'none' | 'position' | 'rotation' | 'scale';
|
|
20
|
+
|
|
21
|
+
const GIZMO_MODE_BUTTONS: Array<{value: GizmoMode; Icon: HeroIcon; title: string}> = [
|
|
22
|
+
{value: 'none', Icon: CursorArrowRaysIcon, title: 'Select (hide stage gizmo)'},
|
|
23
|
+
{value: 'position', Icon: ArrowsPointingOutIcon, title: 'Move the stage node (position)'},
|
|
24
|
+
{value: 'rotation', Icon: ArrowPathIcon, title: 'Rotate the stage node'},
|
|
25
|
+
{value: 'scale', Icon: Square2StackIcon, title: 'Scale the stage node'},
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
/** Props of {@link ViewportPane}. */
|
|
29
|
+
export interface ViewportPaneProps {
|
|
30
|
+
/** The canvas the engine draws into. */
|
|
31
|
+
canvasRef: React.RefObject<HTMLCanvasElement | null>;
|
|
32
|
+
/** The render loop writes the frame rate here. */
|
|
33
|
+
fpsRef: React.RefObject<HTMLSpanElement | null>;
|
|
34
|
+
/** Pixels at the bottom covered by the floating Assets drawer. */
|
|
35
|
+
inset: number;
|
|
36
|
+
/** The transport bar, once the scene exists. */
|
|
37
|
+
transport: React.ReactNode;
|
|
38
|
+
/** The asset drag hovering the viewport, if any. */
|
|
39
|
+
dragHover: {label: string; count: number} | null;
|
|
40
|
+
/** Asset import progress, while importing. */
|
|
41
|
+
assetsLoading: AssetLoadProgress | null;
|
|
42
|
+
/** The open prefab, if any. */
|
|
43
|
+
prefabInfo: PrefabInfo | null;
|
|
44
|
+
/** The open prefab has unsaved edits. */
|
|
45
|
+
prefabDirty: boolean;
|
|
46
|
+
/** Link shown in the top-left corner, outside prefab mode. */
|
|
47
|
+
backLink?: {href: string; label: string};
|
|
48
|
+
/** Undo is possible. */
|
|
49
|
+
canUndo: boolean;
|
|
50
|
+
/** Redo is possible. */
|
|
51
|
+
canRedo: boolean;
|
|
52
|
+
/** An effect is focused. */
|
|
53
|
+
hasFocus: boolean;
|
|
54
|
+
/** The stage gizmo's mode. */
|
|
55
|
+
gizmoMode: GizmoMode;
|
|
56
|
+
/** Emitter shape wireframes are shown. */
|
|
57
|
+
showWireframes: boolean;
|
|
58
|
+
/** Drag handlers of the viewport, for assets dropped from the drawer. */
|
|
59
|
+
dragHandlers: Pick<React.HTMLAttributes<HTMLDivElement>, 'onDragEnter' | 'onDragLeave' | 'onDragOver' | 'onDrop'>;
|
|
60
|
+
/** Undo (`false`) or redo. */
|
|
61
|
+
onHistoryStep(redo: boolean): void;
|
|
62
|
+
/** Pick the stage gizmo's mode. */
|
|
63
|
+
onGizmoMode(mode: GizmoMode): void;
|
|
64
|
+
/** Show or hide the emitter wireframes. */
|
|
65
|
+
onToggleWireframes(): void;
|
|
66
|
+
/** Back from the prefab to the scene. */
|
|
67
|
+
onExitPrefab(): void;
|
|
68
|
+
/** Save the open prefab. */
|
|
69
|
+
onSavePrefab(): void;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** The 3D viewport and what floats over it: toolbars, transport, drag and import feedback. */
|
|
73
|
+
export function ViewportPane({
|
|
74
|
+
canvasRef,
|
|
75
|
+
fpsRef,
|
|
76
|
+
inset,
|
|
77
|
+
transport,
|
|
78
|
+
dragHover,
|
|
79
|
+
assetsLoading,
|
|
80
|
+
prefabInfo,
|
|
81
|
+
prefabDirty,
|
|
82
|
+
backLink,
|
|
83
|
+
canUndo,
|
|
84
|
+
canRedo,
|
|
85
|
+
hasFocus,
|
|
86
|
+
gizmoMode,
|
|
87
|
+
showWireframes,
|
|
88
|
+
dragHandlers,
|
|
89
|
+
onHistoryStep,
|
|
90
|
+
onGizmoMode,
|
|
91
|
+
onToggleWireframes,
|
|
92
|
+
onExitPrefab,
|
|
93
|
+
onSavePrefab,
|
|
94
|
+
}: ViewportPaneProps): React.ReactElement {
|
|
95
|
+
return (
|
|
96
|
+
<div style={{position: 'relative', flex: 1, minWidth: 0}} {...dragHandlers}>
|
|
97
|
+
<canvas
|
|
98
|
+
ref={canvasRef}
|
|
99
|
+
style={{width: '100%', height: '100%', display: 'block', touchAction: 'none', outline: 'none'}}
|
|
100
|
+
/>
|
|
101
|
+
<div
|
|
102
|
+
style={{
|
|
103
|
+
...floatingPanelStyle,
|
|
104
|
+
position: 'absolute',
|
|
105
|
+
bottom: 12 + inset,
|
|
106
|
+
left: 12,
|
|
107
|
+
padding: '4px 10px',
|
|
108
|
+
fontSize: 12,
|
|
109
|
+
color: theme.textDim,
|
|
110
|
+
fontVariantNumeric: 'tabular-nums',
|
|
111
|
+
pointerEvents: 'none',
|
|
112
|
+
fontFamily: theme.font,
|
|
113
|
+
}}
|
|
114
|
+
>
|
|
115
|
+
<span ref={fpsRef}>— FPS</span>
|
|
116
|
+
</div>
|
|
117
|
+
{transport && (
|
|
118
|
+
<div style={{position: 'absolute', bottom: 12 + inset, left: '50%', transform: 'translateX(-50%)'}}>
|
|
119
|
+
{transport}
|
|
120
|
+
</div>
|
|
121
|
+
)}
|
|
122
|
+
{dragHover && (
|
|
123
|
+
<div
|
|
124
|
+
style={{
|
|
125
|
+
position: 'absolute',
|
|
126
|
+
inset: 10,
|
|
127
|
+
border: '2px dashed rgba(120, 165, 255, 0.75)',
|
|
128
|
+
borderRadius: 12,
|
|
129
|
+
pointerEvents: 'none',
|
|
130
|
+
boxShadow: 'inset 0 0 0 1px rgba(120, 165, 255, 0.15)',
|
|
131
|
+
}}
|
|
132
|
+
>
|
|
133
|
+
<div
|
|
134
|
+
style={{
|
|
135
|
+
position: 'absolute',
|
|
136
|
+
left: '50%',
|
|
137
|
+
top: 18,
|
|
138
|
+
transform: 'translateX(-50%)',
|
|
139
|
+
background: 'rgba(7, 11, 22, 0.88)',
|
|
140
|
+
border: `1px solid ${theme.border}`,
|
|
141
|
+
borderRadius: 8,
|
|
142
|
+
padding: '8px 14px',
|
|
143
|
+
fontSize: 13,
|
|
144
|
+
color: theme.text,
|
|
145
|
+
whiteSpace: 'nowrap',
|
|
146
|
+
}}
|
|
147
|
+
>
|
|
148
|
+
Drop {dragHover.count === 1 ? 'effect' : `${dragHover.count} effects`} — {dragHover.label}
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
)}
|
|
152
|
+
{assetsLoading && (
|
|
153
|
+
<div
|
|
154
|
+
style={{
|
|
155
|
+
position: 'absolute',
|
|
156
|
+
inset: 0,
|
|
157
|
+
display: 'flex',
|
|
158
|
+
alignItems: 'center',
|
|
159
|
+
justifyContent: 'center',
|
|
160
|
+
background: 'rgba(7, 11, 22, 0.72)',
|
|
161
|
+
pointerEvents: 'none',
|
|
162
|
+
}}
|
|
163
|
+
>
|
|
164
|
+
<div style={{...floatingPanelStyle, borderRadius: 10, padding: '16px 22px', textAlign: 'center'}}>
|
|
165
|
+
<div style={{fontWeight: 600, marginBottom: 6}}>Importing assets…</div>
|
|
166
|
+
<div style={{fontSize: 13, color: theme.textDim}}>
|
|
167
|
+
{assetsLoading.done} / {assetsLoading.total || '…'} files
|
|
168
|
+
{assetsLoading.failed > 0 ? ` · ${assetsLoading.failed} skipped` : ''}
|
|
169
|
+
</div>
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
172
|
+
)}
|
|
173
|
+
{prefabInfo && (
|
|
174
|
+
<div
|
|
175
|
+
style={{
|
|
176
|
+
...floatingPanelStyle,
|
|
177
|
+
position: 'absolute',
|
|
178
|
+
top: 12,
|
|
179
|
+
left: '50%',
|
|
180
|
+
transform: 'translateX(-50%)',
|
|
181
|
+
display: 'flex',
|
|
182
|
+
alignItems: 'center',
|
|
183
|
+
gap: 8,
|
|
184
|
+
padding: '4px 4px 4px 6px',
|
|
185
|
+
border: `1px solid ${theme.borderActive}`,
|
|
186
|
+
fontSize: 12.5,
|
|
187
|
+
zIndex: 5,
|
|
188
|
+
}}
|
|
189
|
+
>
|
|
190
|
+
<button
|
|
191
|
+
type="button"
|
|
192
|
+
className="qe-hover"
|
|
193
|
+
title="Back to the scene"
|
|
194
|
+
style={{...buttonStyle, padding: '3px 10px'}}
|
|
195
|
+
onClick={onExitPrefab}
|
|
196
|
+
>
|
|
197
|
+
← Scene
|
|
198
|
+
</button>
|
|
199
|
+
<span style={{color: theme.textDim}}>›</span>
|
|
200
|
+
<span style={{fontWeight: 600, whiteSpace: 'nowrap'}}>
|
|
201
|
+
{prefabInfo.name}
|
|
202
|
+
{prefabDirty ? ' *' : ''}
|
|
203
|
+
</span>
|
|
204
|
+
<button
|
|
205
|
+
type="button"
|
|
206
|
+
className="qe-hover"
|
|
207
|
+
title="Overwrite the prefab asset (Ctrl+S)"
|
|
208
|
+
style={saveButtonStyle(prefabDirty, '3px 12px')}
|
|
209
|
+
onClick={onSavePrefab}
|
|
210
|
+
>
|
|
211
|
+
Save
|
|
212
|
+
</button>
|
|
213
|
+
</div>
|
|
214
|
+
)}
|
|
215
|
+
{backLink && !prefabInfo && (
|
|
216
|
+
<a
|
|
217
|
+
className="qe-hover"
|
|
218
|
+
href={backLink.href}
|
|
219
|
+
style={{
|
|
220
|
+
...floatingPanelStyle,
|
|
221
|
+
position: 'absolute',
|
|
222
|
+
top: 12,
|
|
223
|
+
left: 12,
|
|
224
|
+
display: 'inline-flex',
|
|
225
|
+
alignItems: 'center',
|
|
226
|
+
gap: 6,
|
|
227
|
+
padding: '6px 12px',
|
|
228
|
+
fontSize: 13,
|
|
229
|
+
fontWeight: 500,
|
|
230
|
+
color: theme.text,
|
|
231
|
+
textDecoration: 'none',
|
|
232
|
+
fontFamily: theme.font,
|
|
233
|
+
}}
|
|
234
|
+
>
|
|
235
|
+
<ArrowLeftIcon style={iconStyle(14)} />
|
|
236
|
+
{backLink.label.replace(/^←\s*/, '')}
|
|
237
|
+
</a>
|
|
238
|
+
)}
|
|
239
|
+
<div
|
|
240
|
+
style={{
|
|
241
|
+
position: 'absolute',
|
|
242
|
+
top: 12,
|
|
243
|
+
right: 12,
|
|
244
|
+
display: 'flex',
|
|
245
|
+
flexDirection: 'column',
|
|
246
|
+
alignItems: 'flex-end',
|
|
247
|
+
gap: 6,
|
|
248
|
+
}}
|
|
249
|
+
>
|
|
250
|
+
<div style={{...floatingPanelStyle, display: 'flex', gap: 4, padding: 4}}>
|
|
251
|
+
<button
|
|
252
|
+
className="qe-hover"
|
|
253
|
+
style={squareButtonStyle}
|
|
254
|
+
title="Undo (Ctrl+Z)"
|
|
255
|
+
disabled={!canUndo || !hasFocus}
|
|
256
|
+
onClick={() => onHistoryStep(false)}
|
|
257
|
+
>
|
|
258
|
+
<ArrowUturnLeftIcon style={iconStyle(14)} />
|
|
259
|
+
</button>
|
|
260
|
+
<button
|
|
261
|
+
className="qe-hover"
|
|
262
|
+
style={squareButtonStyle}
|
|
263
|
+
title="Redo (Ctrl+Shift+Z)"
|
|
264
|
+
disabled={!canRedo || !hasFocus}
|
|
265
|
+
onClick={() => onHistoryStep(true)}
|
|
266
|
+
>
|
|
267
|
+
<ArrowUturnRightIcon style={iconStyle(14)} />
|
|
268
|
+
</button>
|
|
269
|
+
</div>
|
|
270
|
+
<div style={{...floatingPanelStyle, display: 'flex', flexDirection: 'column', gap: 4, padding: 4}}>
|
|
271
|
+
{GIZMO_MODE_BUTTONS.map((m) => (
|
|
272
|
+
<button
|
|
273
|
+
key={m.value}
|
|
274
|
+
className="qe-hover"
|
|
275
|
+
style={{
|
|
276
|
+
...squareButtonStyle,
|
|
277
|
+
...(gizmoMode === m.value ? activeSquareButtonStyle : {}),
|
|
278
|
+
}}
|
|
279
|
+
title={m.title}
|
|
280
|
+
onClick={() => onGizmoMode(m.value)}
|
|
281
|
+
>
|
|
282
|
+
<m.Icon style={iconStyle(14)} />
|
|
283
|
+
</button>
|
|
284
|
+
))}
|
|
285
|
+
</div>
|
|
286
|
+
<div style={{...floatingPanelStyle, display: 'flex', flexDirection: 'column', gap: 4, padding: 4}}>
|
|
287
|
+
<button
|
|
288
|
+
className="qe-hover"
|
|
289
|
+
style={{...squareButtonStyle, ...(showWireframes ? activeSquareButtonStyle : {})}}
|
|
290
|
+
title="Show emitter shape wireframes"
|
|
291
|
+
disabled={!hasFocus}
|
|
292
|
+
onClick={onToggleWireframes}
|
|
293
|
+
>
|
|
294
|
+
<CubeTransparentIcon style={iconStyle(14)} />
|
|
295
|
+
</button>
|
|
296
|
+
</div>
|
|
297
|
+
</div>
|
|
298
|
+
</div>
|
|
299
|
+
);
|
|
300
|
+
}
|