@fps-games/editor 0.1.2-beta.2 → 0.1.3-beta.2
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/dist/local-editor-harness.d.ts +39 -7
- package/dist/local-editor-harness.d.ts.map +1 -1
- package/dist/local-editor-harness.js +379 -119
- package/dist/local-editor-harness.js.map +1 -1
- package/node_modules/@fps-games/babylon-renderer/dist/index.d.ts +5 -0
- package/node_modules/@fps-games/babylon-renderer/dist/index.d.ts.map +1 -0
- package/node_modules/@fps-games/babylon-renderer/dist/index.js +5 -0
- package/node_modules/@fps-games/babylon-renderer/dist/index.js.map +1 -0
- package/node_modules/@fps-games/babylon-renderer/dist/lighting/directional-light-angles.d.ts +17 -0
- package/node_modules/@fps-games/babylon-renderer/dist/lighting/directional-light-angles.d.ts.map +1 -0
- package/node_modules/@fps-games/babylon-renderer/dist/lighting/directional-light-angles.js +64 -0
- package/node_modules/@fps-games/babylon-renderer/dist/lighting/directional-light-angles.js.map +1 -0
- package/node_modules/@fps-games/babylon-renderer/dist/shadows/planar-shadow-shaders.d.ts +7 -0
- package/node_modules/@fps-games/babylon-renderer/dist/shadows/planar-shadow-shaders.d.ts.map +1 -0
- package/node_modules/@fps-games/babylon-renderer/dist/shadows/planar-shadow-shaders.js +176 -0
- package/node_modules/@fps-games/babylon-renderer/dist/shadows/planar-shadow-shaders.js.map +1 -0
- package/node_modules/@fps-games/babylon-renderer/dist/shadows/planar-shadow-system.d.ts +5 -0
- package/node_modules/@fps-games/babylon-renderer/dist/shadows/planar-shadow-system.d.ts.map +1 -0
- package/node_modules/@fps-games/babylon-renderer/dist/shadows/planar-shadow-system.js +837 -0
- package/node_modules/@fps-games/babylon-renderer/dist/shadows/planar-shadow-system.js.map +1 -0
- package/node_modules/@fps-games/babylon-renderer/dist/shadows/types.d.ts +96 -0
- package/node_modules/@fps-games/babylon-renderer/dist/shadows/types.d.ts.map +1 -0
- package/node_modules/@fps-games/babylon-renderer/dist/shadows/types.js +45 -0
- package/node_modules/@fps-games/babylon-renderer/dist/shadows/types.js.map +1 -0
- package/node_modules/@fps-games/babylon-renderer/package.json +20 -0
- package/node_modules/@fps-games/editor-babylon/dist/editor-sky.d.ts +2 -2
- package/node_modules/@fps-games/editor-babylon/dist/editor-sky.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/editor-sky.js +31 -15
- package/node_modules/@fps-games/editor-babylon/dist/editor-sky.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/editor-world.d.ts +10 -0
- package/node_modules/@fps-games/editor-babylon/dist/editor-world.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/editor-world.js +31 -9
- package/node_modules/@fps-games/editor-babylon/dist/editor-world.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/index.d.ts +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/index.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/index.js +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/index.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/infinite-grid.d.ts +23 -0
- package/node_modules/@fps-games/editor-babylon/dist/infinite-grid.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/infinite-grid.js +34 -4
- package/node_modules/@fps-games/editor-babylon/dist/infinite-grid.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/material-property-adapter.d.ts +3 -1
- package/node_modules/@fps-games/editor-babylon/dist/material-property-adapter.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/material-property-adapter.js +166 -0
- package/node_modules/@fps-games/editor-babylon/dist/material-property-adapter.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/projection.d.ts +12 -1
- package/node_modules/@fps-games/editor-babylon/dist/projection.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/projection.js +231 -32
- package/node_modules/@fps-games/editor-babylon/dist/projection.js.map +1 -1
- package/node_modules/@fps-games/editor-babylon/dist/shadow-preview-controller.d.ts +22 -0
- package/node_modules/@fps-games/editor-babylon/dist/shadow-preview-controller.d.ts.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/shadow-preview-controller.js +71 -0
- package/node_modules/@fps-games/editor-babylon/dist/shadow-preview-controller.js.map +1 -0
- package/node_modules/@fps-games/editor-babylon/dist/types.d.ts +2 -1
- package/node_modules/@fps-games/editor-babylon/dist/types.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-babylon/package.json +5 -4
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panel-registry.d.ts +3 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panel-registry.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panel-registry.js +7 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panel-registry.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.d.ts +8 -3
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.js +645 -20
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-types.d.ts +147 -3
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-types.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-workbench.d.ts +6 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-workbench.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-workbench.js +1 -0
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-workbench.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.d.ts +2 -4
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.js +1060 -86
- package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.js.map +1 -1
- package/node_modules/@fps-games/editor-browser/package.json +2 -2
- package/node_modules/@fps-games/editor-core/package.json +2 -2
- package/node_modules/@fps-games/editor-forge-play/package.json +2 -2
- package/node_modules/@fps-games/editor-protocol/dist/index.d.ts +61 -0
- package/node_modules/@fps-games/editor-protocol/dist/index.d.ts.map +1 -1
- package/node_modules/@fps-games/editor-protocol/dist/index.js +157 -0
- package/node_modules/@fps-games/editor-protocol/dist/index.js.map +1 -1
- package/node_modules/@fps-games/editor-protocol/package.json +1 -1
- package/package.json +8 -6
|
@@ -1,10 +1,20 @@
|
|
|
1
|
-
import { createLocalEditorIcon } from './local-editor-ui-icons';
|
|
1
|
+
import { createLocalEditorIcon, isLocalEditorIconName, } from './local-editor-ui-icons';
|
|
2
2
|
import { createBadge, createAssetList, createDockTab, createEditorInputStyle, createEmptyState, createListItemBlock, createPanelHeader, createPropertyRow, createToolbarButton, createTreeView, createTreeViewItem, } from './local-editor-ui-primitives';
|
|
3
3
|
import { clearElement, toTitle } from './local-editor-ui-shared';
|
|
4
|
-
const
|
|
4
|
+
const BOTTOM_DOCK_TAB_ICONS = {
|
|
5
5
|
assets: 'asset',
|
|
6
6
|
history: 'history',
|
|
7
7
|
};
|
|
8
|
+
const RIGHT_DOCK_TAB_ICONS = {
|
|
9
|
+
inspector: 'inspector',
|
|
10
|
+
rendering: 'world',
|
|
11
|
+
};
|
|
12
|
+
const ASSET_BROWSER_TABS = [
|
|
13
|
+
{ id: 'all', label: '全部', title: '显示全部项目资产' },
|
|
14
|
+
{ id: 'models', label: '模型', title: '显示模型资产' },
|
|
15
|
+
{ id: 'materials', label: '材质', title: '显示项目材质资产' },
|
|
16
|
+
{ id: 'textures', label: '贴图', title: '显示贴图资产' },
|
|
17
|
+
];
|
|
8
18
|
export function renderHierarchyPanel(doc, panel, state, rename, drop) {
|
|
9
19
|
clearElement(panel);
|
|
10
20
|
const createGroupButton = createToolbarButton(doc, '+ Empty', 'object');
|
|
@@ -59,15 +69,15 @@ export function renderHierarchyPanel(doc, panel, state, rename, drop) {
|
|
|
59
69
|
}
|
|
60
70
|
panel.appendChild(list);
|
|
61
71
|
}
|
|
62
|
-
export function renderBottomDockPanel(doc, panel, state, activeTab) {
|
|
72
|
+
export function renderBottomDockPanel(doc, panel, state, activeTab, activeAssetBrowserTab = 'all') {
|
|
63
73
|
clearElement(panel);
|
|
64
74
|
appendDockTabs(doc, panel, activeTab);
|
|
65
75
|
if (activeTab === 'history')
|
|
66
76
|
renderHistoryPanel(doc, panel, state.session?.history ?? null);
|
|
67
77
|
else
|
|
68
|
-
renderAssetBrowserContent(doc, panel, state);
|
|
78
|
+
renderAssetBrowserContent(doc, panel, state, 'list', activeAssetBrowserTab);
|
|
69
79
|
}
|
|
70
|
-
export function renderWorkbenchBottomDockPanel(doc, panel, state, activeTab, panels = []) {
|
|
80
|
+
export function renderWorkbenchBottomDockPanel(doc, panel, state, activeTab, panels = [], activeAssetBrowserTab = 'all') {
|
|
71
81
|
clearElement(panel);
|
|
72
82
|
const tabHeader = doc.createElement('div');
|
|
73
83
|
tabHeader.style.cssText = [
|
|
@@ -85,7 +95,7 @@ export function renderWorkbenchBottomDockPanel(doc, panel, state, activeTab, pan
|
|
|
85
95
|
{ id: 'history', title: '历史', area: 'bottom' },
|
|
86
96
|
];
|
|
87
97
|
for (const tabPanel of dockTabs) {
|
|
88
|
-
const button = createDockTab(doc, tabPanel.title, activeTab === tabPanel.id,
|
|
98
|
+
const button = createDockTab(doc, tabPanel.title, activeTab === tabPanel.id, BOTTOM_DOCK_TAB_ICONS[tabPanel.id]);
|
|
89
99
|
button.dataset.editorDockTab = tabPanel.id;
|
|
90
100
|
tabHeader.appendChild(button);
|
|
91
101
|
}
|
|
@@ -96,7 +106,7 @@ export function renderWorkbenchBottomDockPanel(doc, panel, state, activeTab, pan
|
|
|
96
106
|
if (activeTab === 'history')
|
|
97
107
|
renderHistoryPanel(doc, content, state.session?.history ?? null);
|
|
98
108
|
else
|
|
99
|
-
renderAssetBrowserContent(doc, content, state, 'grid');
|
|
109
|
+
renderAssetBrowserContent(doc, content, state, 'grid', activeAssetBrowserTab);
|
|
100
110
|
}
|
|
101
111
|
function appendDockTabs(doc, panel, activeTab) {
|
|
102
112
|
const tabs = doc.createElement('div');
|
|
@@ -113,7 +123,7 @@ function appendDockTabs(doc, panel, activeTab) {
|
|
|
113
123
|
};
|
|
114
124
|
for (const tab of ['assets', 'history']) {
|
|
115
125
|
const active = activeTab === tab;
|
|
116
|
-
const button = createDockTab(doc, labels[tab], active,
|
|
126
|
+
const button = createDockTab(doc, labels[tab], active, BOTTOM_DOCK_TAB_ICONS[tab]);
|
|
117
127
|
button.dataset.editorDockTab = tab;
|
|
118
128
|
button.style.border = '0';
|
|
119
129
|
button.style.borderBottom = `2px solid ${active ? 'var(--fps-editor-accent-strong)' : 'transparent'}`;
|
|
@@ -124,18 +134,299 @@ function appendDockTabs(doc, panel, activeTab) {
|
|
|
124
134
|
}
|
|
125
135
|
panel.appendChild(tabs);
|
|
126
136
|
}
|
|
137
|
+
export function renderWorkbenchRightDockTabs(doc, panel, activeTab, panels = []) {
|
|
138
|
+
clearElement(panel);
|
|
139
|
+
panel.style.cssText = [
|
|
140
|
+
'height:35px',
|
|
141
|
+
'display:flex',
|
|
142
|
+
'align-items:stretch',
|
|
143
|
+
'border-bottom:1px solid var(--fps-editor-divider)',
|
|
144
|
+
'background:var(--fps-editor-chrome-dark)',
|
|
145
|
+
'flex:0 0 auto',
|
|
146
|
+
].join(';');
|
|
147
|
+
const dockTabs = panels.length > 0
|
|
148
|
+
? panels.filter((panelDescriptor) => (panelDescriptor.id === 'inspector' || panelDescriptor.id === 'rendering'))
|
|
149
|
+
: [
|
|
150
|
+
{ id: 'inspector', title: '检查器', area: 'right' },
|
|
151
|
+
{ id: 'rendering', title: '渲染', area: 'right' },
|
|
152
|
+
];
|
|
153
|
+
for (const tabPanel of dockTabs) {
|
|
154
|
+
const button = createDockTab(doc, tabPanel.title, activeTab === tabPanel.id, RIGHT_DOCK_TAB_ICONS[tabPanel.id]);
|
|
155
|
+
button.dataset.editorRightDockTab = tabPanel.id;
|
|
156
|
+
tabHeaderButtonStyle(button);
|
|
157
|
+
panel.appendChild(button);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
export function renderRenderingPanel(doc, panel, state) {
|
|
161
|
+
clearElement(panel);
|
|
162
|
+
const rendering = state.renderingPanel ?? null;
|
|
163
|
+
panel.appendChild(createPanelHeader(doc, rendering?.title ?? 'Rendering', createRenderingPanelActions(doc, rendering), 'world'));
|
|
164
|
+
if (!rendering || rendering.sections.length === 0) {
|
|
165
|
+
panel.appendChild(createEmptyState(doc, '当前项目没有提供渲染设置。'));
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (rendering.dirty || rendering.status) {
|
|
169
|
+
const status = doc.createElement('div');
|
|
170
|
+
status.style.cssText = [
|
|
171
|
+
'display:flex',
|
|
172
|
+
'align-items:center',
|
|
173
|
+
'gap:6px',
|
|
174
|
+
'margin:0 0 8px',
|
|
175
|
+
'min-height:20px',
|
|
176
|
+
].join(';');
|
|
177
|
+
if (rendering.dirty)
|
|
178
|
+
status.appendChild(createBadge(doc, 'UNSAVED', { compact: true, tone: 'warning' }));
|
|
179
|
+
if (rendering.status) {
|
|
180
|
+
const message = doc.createElement('span');
|
|
181
|
+
message.textContent = rendering.status;
|
|
182
|
+
message.style.cssText = [
|
|
183
|
+
`color:${rendering.statusTone === 'error' ? 'var(--fps-editor-danger-text)' : rendering.statusTone === 'warning' ? 'var(--fps-editor-warn)' : 'var(--fps-editor-muted)'}`,
|
|
184
|
+
'font-size:11px',
|
|
185
|
+
'font-weight:800',
|
|
186
|
+
'min-width:0',
|
|
187
|
+
'overflow:hidden',
|
|
188
|
+
'text-overflow:ellipsis',
|
|
189
|
+
'white-space:nowrap',
|
|
190
|
+
].join(';');
|
|
191
|
+
status.appendChild(message);
|
|
192
|
+
}
|
|
193
|
+
panel.appendChild(status);
|
|
194
|
+
}
|
|
195
|
+
if (rendering.summary) {
|
|
196
|
+
const summary = doc.createElement('div');
|
|
197
|
+
summary.textContent = rendering.summary;
|
|
198
|
+
summary.style.cssText = [
|
|
199
|
+
'margin:0 0 8px',
|
|
200
|
+
'color:var(--fps-editor-muted)',
|
|
201
|
+
'font-size:11px',
|
|
202
|
+
'line-height:1.45',
|
|
203
|
+
].join(';');
|
|
204
|
+
panel.appendChild(summary);
|
|
205
|
+
}
|
|
206
|
+
for (const section of rendering.sections) {
|
|
207
|
+
panel.appendChild(createRenderingSectionBlock(doc, section));
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
function createRenderingPanelActions(doc, rendering) {
|
|
211
|
+
return (rendering?.actions ?? []).map(action => {
|
|
212
|
+
const icon = action.icon && isLocalEditorIconName(action.icon) ? action.icon : 'world';
|
|
213
|
+
const button = createToolbarButton(doc, action.label, icon);
|
|
214
|
+
button.type = 'button';
|
|
215
|
+
button.dataset.editorRenderingAction = action.id;
|
|
216
|
+
button.disabled = action.disabled === true;
|
|
217
|
+
button.title = action.tooltip ?? action.label;
|
|
218
|
+
button.dataset.editorTooltip = button.title;
|
|
219
|
+
button.style.padding = '3px 7px';
|
|
220
|
+
button.style.fontSize = '11px';
|
|
221
|
+
return button;
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
function tabHeaderButtonStyle(button) {
|
|
225
|
+
button.style.borderTop = '0';
|
|
226
|
+
button.style.borderLeft = '0';
|
|
227
|
+
button.style.borderRadius = '0';
|
|
228
|
+
button.style.minWidth = '0';
|
|
229
|
+
}
|
|
230
|
+
function createRenderingSectionBlock(doc, section) {
|
|
231
|
+
const block = doc.createElement('section');
|
|
232
|
+
block.dataset.editorRenderingSection = section.id;
|
|
233
|
+
block.style.cssText = [
|
|
234
|
+
'margin:0 0 10px',
|
|
235
|
+
'padding:8px',
|
|
236
|
+
'border:1px solid var(--fps-editor-border)',
|
|
237
|
+
'border-radius:3px',
|
|
238
|
+
'background:var(--fps-editor-panel-soft)',
|
|
239
|
+
].join(';');
|
|
240
|
+
const header = doc.createElement('div');
|
|
241
|
+
header.style.cssText = 'display:flex;align-items:flex-start;justify-content:space-between;gap:8px;margin:0 0 8px';
|
|
242
|
+
const title = doc.createElement('div');
|
|
243
|
+
title.style.cssText = 'min-width:0';
|
|
244
|
+
const heading = doc.createElement('h3');
|
|
245
|
+
heading.textContent = section.title;
|
|
246
|
+
heading.style.cssText = 'margin:0;color:var(--fps-editor-text-strong);font-size:12px;font-weight:900';
|
|
247
|
+
title.appendChild(heading);
|
|
248
|
+
if (section.summary) {
|
|
249
|
+
const summary = doc.createElement('div');
|
|
250
|
+
summary.textContent = section.summary;
|
|
251
|
+
summary.style.cssText = 'margin-top:3px;color:var(--fps-editor-muted);font-size:11px;line-height:1.35';
|
|
252
|
+
title.appendChild(summary);
|
|
253
|
+
}
|
|
254
|
+
header.appendChild(title);
|
|
255
|
+
header.appendChild(createBadge(doc, `${section.systems.length}`, { compact: true }));
|
|
256
|
+
block.appendChild(header);
|
|
257
|
+
const list = doc.createElement('div');
|
|
258
|
+
list.style.cssText = 'display:flex;flex-direction:column;gap:8px';
|
|
259
|
+
for (const system of section.systems) {
|
|
260
|
+
list.appendChild(createRenderingSystemBlock(doc, section.id, system));
|
|
261
|
+
}
|
|
262
|
+
block.appendChild(list);
|
|
263
|
+
return block;
|
|
264
|
+
}
|
|
265
|
+
function createRenderingSystemBlock(doc, sectionId, system) {
|
|
266
|
+
const block = createListItemBlock(doc);
|
|
267
|
+
block.dataset.editorRenderingSystem = system.id;
|
|
268
|
+
block.dataset.editorRenderingSystemKind = system.kind;
|
|
269
|
+
const header = doc.createElement('div');
|
|
270
|
+
header.style.cssText = 'display:flex;align-items:flex-start;justify-content:space-between;gap:8px;margin:0 0 8px';
|
|
271
|
+
const title = doc.createElement('div');
|
|
272
|
+
title.style.cssText = 'min-width:0';
|
|
273
|
+
const label = doc.createElement('div');
|
|
274
|
+
label.textContent = system.label;
|
|
275
|
+
label.style.cssText = 'color:var(--fps-editor-text-strong);font-size:12px;font-weight:900;line-height:1.25';
|
|
276
|
+
title.appendChild(label);
|
|
277
|
+
if (system.summary) {
|
|
278
|
+
const summary = doc.createElement('div');
|
|
279
|
+
summary.textContent = system.summary;
|
|
280
|
+
summary.style.cssText = 'margin-top:3px;color:var(--fps-editor-muted);font-size:11px;line-height:1.35';
|
|
281
|
+
title.appendChild(summary);
|
|
282
|
+
}
|
|
283
|
+
header.appendChild(title);
|
|
284
|
+
const badges = doc.createElement('div');
|
|
285
|
+
badges.style.cssText = 'display:flex;align-items:center;gap:4px;flex:0 0 auto';
|
|
286
|
+
if (system.active != null)
|
|
287
|
+
badges.appendChild(createBadge(doc, system.active ? 'ON' : 'OFF', { compact: true, tone: system.active ? 'success' : 'default' }));
|
|
288
|
+
if (system.readOnly)
|
|
289
|
+
badges.appendChild(createBadge(doc, 'READONLY', { compact: true }));
|
|
290
|
+
if (system.status)
|
|
291
|
+
badges.appendChild(createBadge(doc, system.status, { compact: true, tone: system.active === false ? 'warning' : 'default' }));
|
|
292
|
+
header.appendChild(badges);
|
|
293
|
+
block.appendChild(header);
|
|
294
|
+
const propertyGrid = doc.createElement('div');
|
|
295
|
+
propertyGrid.style.cssText = 'display:flex;flex-direction:column;gap:6px';
|
|
296
|
+
for (const property of system.properties) {
|
|
297
|
+
propertyGrid.appendChild(createRenderingPropertyRow(doc, sectionId, system.id, property, system.readOnly === true));
|
|
298
|
+
}
|
|
299
|
+
block.appendChild(propertyGrid);
|
|
300
|
+
return block;
|
|
301
|
+
}
|
|
302
|
+
function createRenderingPropertyRow(doc, sectionId, systemId, property, systemReadOnly) {
|
|
303
|
+
const inspectorProperty = createInspectorPropertyFromRenderingProperty(property, systemReadOnly);
|
|
304
|
+
const control = createRenderingPropertyControl(doc, sectionId, systemId, property, inspectorProperty);
|
|
305
|
+
const access = getInspectorPropertyAccess(inspectorProperty);
|
|
306
|
+
const effect = getInspectorPropertyEffect(inspectorProperty);
|
|
307
|
+
const row = createPropertyRow(doc, inspectorProperty.label, control);
|
|
308
|
+
row.dataset.editorInspectorAccess = access;
|
|
309
|
+
row.dataset.editorInspectorEffect = effect;
|
|
310
|
+
row.title = createInspectorStatusTitle(access, effect, inspectorProperty.tooltip ?? inspectorProperty.disabledReason);
|
|
311
|
+
row.dataset.editorTooltip = row.title;
|
|
312
|
+
const label = row.firstElementChild;
|
|
313
|
+
if (label)
|
|
314
|
+
label.style.cssText = createInspectorPropertyLabelStyle(access, effect);
|
|
315
|
+
return row;
|
|
316
|
+
}
|
|
317
|
+
function createRenderingPropertyControl(doc, sectionId, systemId, property, inspectorProperty) {
|
|
318
|
+
const target = createRenderingInspectorTarget(systemId);
|
|
319
|
+
if (isInspectorPropertyEditable(inspectorProperty) && property.control === 'string-list') {
|
|
320
|
+
return createRenderingStringListControl(doc, sectionId, systemId, property, inspectorProperty);
|
|
321
|
+
}
|
|
322
|
+
let control = renderInspectorControl(doc, builtinInspectorControlRegistrations, target, inspectorProperty);
|
|
323
|
+
bindRenderedInspectorControlsToRendering(control, sectionId, systemId, property);
|
|
324
|
+
if (property.unit && isInspectorPropertyEditable(inspectorProperty) && property.control === 'number') {
|
|
325
|
+
const wrapper = doc.createElement('div');
|
|
326
|
+
wrapper.style.cssText = 'display:flex;align-items:center;gap:6px;min-width:0';
|
|
327
|
+
control.style.minWidth = '0';
|
|
328
|
+
control.style.flex = '1 1 auto';
|
|
329
|
+
wrapper.appendChild(control);
|
|
330
|
+
wrapper.appendChild(createPropertyUnitLabel(doc, property.unit));
|
|
331
|
+
return wrapper;
|
|
332
|
+
}
|
|
333
|
+
return control;
|
|
334
|
+
}
|
|
335
|
+
function bindRenderingControl(element, sectionId, systemId, property, control) {
|
|
336
|
+
element.dataset.editorRenderingProperty = 'true';
|
|
337
|
+
element.dataset.renderingSectionId = sectionId;
|
|
338
|
+
element.dataset.renderingSystemId = systemId;
|
|
339
|
+
element.dataset.renderingPath = property.path;
|
|
340
|
+
element.dataset.renderingControl = control;
|
|
341
|
+
element.dataset.renderingValueType = property.valueType;
|
|
342
|
+
element.dataset.renderingCommitMode = property.commitMode ?? (control === 'string-list' ? 'blur' : 'live');
|
|
343
|
+
if (property.disabled) {
|
|
344
|
+
element.disabled = true;
|
|
345
|
+
element.title = property.disabledReason ?? '';
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
function createInspectorPropertyFromRenderingProperty(property, systemReadOnly) {
|
|
349
|
+
const readOnly = systemReadOnly || property.readOnly === true || property.control === 'readonly' || property.disabled === true;
|
|
350
|
+
return {
|
|
351
|
+
path: property.path,
|
|
352
|
+
label: property.label,
|
|
353
|
+
valueType: property.valueType === 'string-list' ? 'string' : property.valueType,
|
|
354
|
+
control: property.control === 'string-list'
|
|
355
|
+
? readOnly ? 'readonly' : 'string'
|
|
356
|
+
: property.control,
|
|
357
|
+
value: property.value,
|
|
358
|
+
readOnly,
|
|
359
|
+
persistence: readOnly ? 'readonly' : 'document',
|
|
360
|
+
commitMode: property.commitMode ?? (property.control === 'string-list' ? 'blur' : 'live'),
|
|
361
|
+
tooltip: property.tooltip,
|
|
362
|
+
effect: property.disabled ? 'unsupported' : undefined,
|
|
363
|
+
disabledReason: property.disabledReason,
|
|
364
|
+
placeholder: property.placeholder,
|
|
365
|
+
min: property.min,
|
|
366
|
+
max: property.max,
|
|
367
|
+
step: property.step,
|
|
368
|
+
tags: property.tags,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
function createRenderingInspectorTarget(systemId) {
|
|
372
|
+
return {
|
|
373
|
+
targetIds: [systemId],
|
|
374
|
+
activeId: systemId,
|
|
375
|
+
selection: {
|
|
376
|
+
targetIds: [systemId],
|
|
377
|
+
activeId: systemId,
|
|
378
|
+
targetKind: 'rendering-system',
|
|
379
|
+
},
|
|
380
|
+
sections: [],
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
function bindRenderedInspectorControlsToRendering(control, sectionId, systemId, property) {
|
|
384
|
+
const fields = control.matches('input, textarea, select')
|
|
385
|
+
? [control]
|
|
386
|
+
: Array.from(control.querySelectorAll('input, textarea, select'));
|
|
387
|
+
for (const field of fields) {
|
|
388
|
+
if (!(field instanceof HTMLInputElement || field instanceof HTMLTextAreaElement))
|
|
389
|
+
continue;
|
|
390
|
+
bindRenderingControl(field, sectionId, systemId, property, property.control);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
function createRenderingStringListControl(doc, sectionId, systemId, property, inspectorProperty) {
|
|
394
|
+
const input = doc.createElement('textarea');
|
|
395
|
+
input.value = Array.isArray(property.value) ? property.value.map(String).join('\n') : String(property.value ?? '');
|
|
396
|
+
input.placeholder = property.placeholder ?? 'One pattern per line';
|
|
397
|
+
input.rows = 3;
|
|
398
|
+
input.style.cssText = [
|
|
399
|
+
createInspectorInputStyle(),
|
|
400
|
+
'height:auto',
|
|
401
|
+
'min-height:66px',
|
|
402
|
+
'resize:vertical',
|
|
403
|
+
'padding:6px 8px',
|
|
404
|
+
'line-height:1.35',
|
|
405
|
+
].join(';');
|
|
406
|
+
input.title = inspectorProperty.tooltip ?? inspectorProperty.label;
|
|
407
|
+
input.dataset.editorTooltip = input.title;
|
|
408
|
+
bindRenderingControl(input, sectionId, systemId, property, 'string-list');
|
|
409
|
+
return input;
|
|
410
|
+
}
|
|
411
|
+
function createPropertyUnitLabel(doc, unit) {
|
|
412
|
+
const label = doc.createElement('span');
|
|
413
|
+
label.textContent = unit;
|
|
414
|
+
label.style.cssText = 'flex:0 0 auto;color:var(--fps-editor-muted);font-size:10px;font-weight:800';
|
|
415
|
+
return label;
|
|
416
|
+
}
|
|
127
417
|
function createHeadingLabel(doc, text) {
|
|
128
418
|
const label = doc.createElement('span');
|
|
129
419
|
label.textContent = text;
|
|
130
420
|
label.style.cssText = 'min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap';
|
|
131
421
|
return label;
|
|
132
422
|
}
|
|
133
|
-
function renderAssetBrowserContent(doc, panel, state, variant = 'list') {
|
|
423
|
+
function renderAssetBrowserContent(doc, panel, state, variant = 'list', activeTab = 'all') {
|
|
134
424
|
const title = doc.createElement('h2');
|
|
135
425
|
title.style.cssText = 'display:flex;align-items:center;gap:6px;font-size:13px;margin:0 0 8px;font-weight:800;color:var(--fps-editor-text-strong)';
|
|
136
426
|
title.appendChild(createLocalEditorIcon(doc, 'asset'));
|
|
137
427
|
title.appendChild(createHeadingLabel(doc, '资产浏览器'));
|
|
138
428
|
panel.appendChild(title);
|
|
429
|
+
appendAssetBrowserTabs(doc, panel, state, activeTab);
|
|
139
430
|
const filter = doc.createElement('input');
|
|
140
431
|
filter.type = 'search';
|
|
141
432
|
filter.placeholder = '筛选资产';
|
|
@@ -153,8 +444,9 @@ function renderAssetBrowserContent(doc, panel, state, variant = 'list') {
|
|
|
153
444
|
'margin:0 0 8px',
|
|
154
445
|
].join(';');
|
|
155
446
|
panel.appendChild(filter);
|
|
447
|
+
const tabAssets = filterAssetsByBrowserTab(state.assets, activeTab);
|
|
156
448
|
const normalizedFilter = state.assetFilter.trim().toLowerCase();
|
|
157
|
-
const filteredAssets =
|
|
449
|
+
const filteredAssets = tabAssets
|
|
158
450
|
.filter((asset) => {
|
|
159
451
|
if (!normalizedFilter)
|
|
160
452
|
return true;
|
|
@@ -173,33 +465,38 @@ function renderAssetBrowserContent(doc, panel, state, variant = 'list') {
|
|
|
173
465
|
})
|
|
174
466
|
.slice(0, 80);
|
|
175
467
|
const count = doc.createElement('div');
|
|
176
|
-
|
|
468
|
+
const activeTabLabel = ASSET_BROWSER_TABS.find(tab => tab.id === activeTab)?.label ?? '资产';
|
|
469
|
+
count.textContent = activeTab === 'all' && state.assetCountLabel
|
|
177
470
|
? `${filteredAssets.length} / ${state.assetCountLabel}`
|
|
178
|
-
: `${filteredAssets.length} / ${
|
|
471
|
+
: `${filteredAssets.length} / ${tabAssets.length} 个${activeTabLabel}资产`;
|
|
179
472
|
count.style.cssText = 'color:var(--fps-editor-muted);font-size:11px;margin-bottom:6px';
|
|
180
473
|
panel.appendChild(count);
|
|
181
474
|
const list = createAssetList(doc, variant === 'grid');
|
|
182
475
|
for (const asset of filteredAssets) {
|
|
476
|
+
const selected = state.selectedAssetId === asset.id;
|
|
477
|
+
const hasPreview = !!asset.preview;
|
|
183
478
|
const button = doc.createElement('button');
|
|
184
479
|
button.type = 'button';
|
|
185
480
|
button.dataset.editorAssetId = asset.id;
|
|
186
481
|
button.disabled = asset.disabled ?? false;
|
|
482
|
+
button.setAttribute('aria-selected', selected ? 'true' : 'false');
|
|
187
483
|
button.style.cssText = [
|
|
188
484
|
'width:100%',
|
|
189
|
-
`min-height:${variant === 'grid' ? '48px' : '32px'}`,
|
|
485
|
+
`min-height:${variant === 'grid' ? (hasPreview ? '66px' : '48px') : (hasPreview ? '38px' : '32px')}`,
|
|
190
486
|
'text-align:left',
|
|
191
|
-
'
|
|
192
|
-
|
|
487
|
+
`padding:${hasPreview ? '7px 8px' : '6px 8px'}`,
|
|
488
|
+
`border:1px solid ${selected ? 'var(--fps-editor-accent-strong)' : 'var(--fps-editor-border)'}`,
|
|
193
489
|
'border-radius:3px',
|
|
194
|
-
'
|
|
490
|
+
`background:${selected ? 'var(--fps-editor-accent-soft)' : 'var(--fps-editor-asset-card-bg)'}`,
|
|
195
491
|
'color:var(--fps-editor-text)',
|
|
196
492
|
'display:flex',
|
|
197
493
|
'align-items:center',
|
|
198
494
|
'gap:8px',
|
|
199
495
|
'font-size:12px',
|
|
200
|
-
'cursor:pointer',
|
|
496
|
+
asset.disabled ? 'cursor:not-allowed' : 'cursor:pointer',
|
|
497
|
+
asset.disabled ? 'opacity:0.72' : '',
|
|
201
498
|
].join(';');
|
|
202
|
-
const
|
|
499
|
+
const preview = createAssetPreviewElement(doc, asset, variant);
|
|
203
500
|
const body = doc.createElement('div');
|
|
204
501
|
body.style.cssText = 'min-width:0;flex:1';
|
|
205
502
|
const label = doc.createElement('div');
|
|
@@ -210,12 +507,178 @@ function renderAssetBrowserContent(doc, panel, state, variant = 'list') {
|
|
|
210
507
|
meta.style.cssText = 'color:var(--fps-editor-muted);font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap';
|
|
211
508
|
body.appendChild(label);
|
|
212
509
|
body.appendChild(meta);
|
|
213
|
-
button.appendChild(
|
|
510
|
+
button.appendChild(preview);
|
|
214
511
|
button.appendChild(body);
|
|
215
512
|
list.appendChild(button);
|
|
216
513
|
}
|
|
514
|
+
if (filteredAssets.length === 0) {
|
|
515
|
+
list.appendChild(createEmptyState(doc, normalizedFilter ? '没有匹配的资产。' : '暂无该类型资产。'));
|
|
516
|
+
}
|
|
217
517
|
panel.appendChild(list);
|
|
218
518
|
}
|
|
519
|
+
export function createAssetPreviewElement(doc, asset, variant) {
|
|
520
|
+
const size = variant === 'modal' ? 220 : variant === 'grid' ? 46 : 24;
|
|
521
|
+
const preview = asset.preview;
|
|
522
|
+
if (!preview)
|
|
523
|
+
return createLocalEditorIcon(doc, 'asset', { size: variant === 'grid' ? 18 : 16 });
|
|
524
|
+
const frame = doc.createElement('div');
|
|
525
|
+
frame.style.cssText = [
|
|
526
|
+
`width:${size}px`,
|
|
527
|
+
`height:${size}px`,
|
|
528
|
+
`flex:0 0 ${size}px`,
|
|
529
|
+
'display:flex',
|
|
530
|
+
'align-items:center',
|
|
531
|
+
'justify-content:center',
|
|
532
|
+
'overflow:hidden',
|
|
533
|
+
'border:1px solid var(--fps-editor-border)',
|
|
534
|
+
`border-radius:${variant === 'modal' ? '6px' : '3px'}`,
|
|
535
|
+
'background:var(--fps-editor-field)',
|
|
536
|
+
'box-sizing:border-box',
|
|
537
|
+
].join(';');
|
|
538
|
+
if (preview.kind === 'image') {
|
|
539
|
+
const image = doc.createElement('img');
|
|
540
|
+
image.src = preview.url;
|
|
541
|
+
image.alt = preview.alt ?? asset.label;
|
|
542
|
+
image.loading = 'lazy';
|
|
543
|
+
image.style.cssText = [
|
|
544
|
+
'width:100%',
|
|
545
|
+
'height:100%',
|
|
546
|
+
`object-fit:${preview.fit ?? 'cover'}`,
|
|
547
|
+
'display:block',
|
|
548
|
+
].join(';');
|
|
549
|
+
frame.appendChild(image);
|
|
550
|
+
return frame;
|
|
551
|
+
}
|
|
552
|
+
const sphere = doc.createElement('div');
|
|
553
|
+
const metallic = clamp01(preview.metallic ?? 0);
|
|
554
|
+
const roughness = clamp01(preview.roughness ?? 0.55);
|
|
555
|
+
const emissionIntensity = clampNumber(preview.emissionIntensity ?? 0, 0, 8);
|
|
556
|
+
const baseColor = formatAssetPreviewColor(preview.baseColor, '#8f9aa8');
|
|
557
|
+
const emissionColor = formatAssetPreviewColor(preview.emissionColor, '#ffffff');
|
|
558
|
+
sphere.style.cssText = [
|
|
559
|
+
'position:relative',
|
|
560
|
+
'width:82%',
|
|
561
|
+
'height:82%',
|
|
562
|
+
'overflow:hidden',
|
|
563
|
+
'border-radius:999px',
|
|
564
|
+
`background:${baseColor}`,
|
|
565
|
+
'border:1px solid rgba(255,255,255,0.22)',
|
|
566
|
+
'box-shadow:' + [
|
|
567
|
+
`inset -5px -7px 12px rgba(0,0,0,${(0.26 + roughness * 0.14).toFixed(2)})`,
|
|
568
|
+
`inset 5px 5px 10px rgba(255,255,255,${(0.12 + metallic * 0.2).toFixed(2)})`,
|
|
569
|
+
emissionIntensity > 0 ? `0 0 ${Math.round(6 + emissionIntensity * 5)}px ${emissionColor}` : '0 1px 2px rgba(0,0,0,0.4)',
|
|
570
|
+
].join(','),
|
|
571
|
+
].join(';');
|
|
572
|
+
if (preview.textureUrl) {
|
|
573
|
+
const image = doc.createElement('img');
|
|
574
|
+
image.src = preview.textureUrl;
|
|
575
|
+
image.alt = '';
|
|
576
|
+
image.style.cssText = [
|
|
577
|
+
'position:absolute',
|
|
578
|
+
'inset:0',
|
|
579
|
+
'width:100%',
|
|
580
|
+
'height:100%',
|
|
581
|
+
'object-fit:cover',
|
|
582
|
+
'opacity:0.62',
|
|
583
|
+
'mix-blend-mode:multiply',
|
|
584
|
+
].join(';');
|
|
585
|
+
sphere.appendChild(image);
|
|
586
|
+
}
|
|
587
|
+
const shading = doc.createElement('div');
|
|
588
|
+
const highlightAlpha = clampNumber(0.86 - roughness * 0.48 + metallic * 0.12, 0.25, 0.92);
|
|
589
|
+
const highlightEnd = Math.round(9 + roughness * 12);
|
|
590
|
+
const shadowAlpha = clampNumber(0.28 + roughness * 0.14 + metallic * 0.16, 0.2, 0.62);
|
|
591
|
+
const rimAlpha = clampNumber(0.12 + metallic * 0.36, 0.08, 0.52);
|
|
592
|
+
shading.style.cssText = [
|
|
593
|
+
'position:absolute',
|
|
594
|
+
'inset:0',
|
|
595
|
+
'border-radius:inherit',
|
|
596
|
+
'pointer-events:none',
|
|
597
|
+
'background:' + [
|
|
598
|
+
`radial-gradient(circle at 31% 23%, rgba(255,255,255,${highlightAlpha.toFixed(2)}) 0 ${highlightEnd}%, rgba(255,255,255,0) ${highlightEnd + 17}%)`,
|
|
599
|
+
`radial-gradient(circle at 70% 82%, rgba(0,0,0,${shadowAlpha.toFixed(2)}) 0 26%, rgba(0,0,0,0) 58%)`,
|
|
600
|
+
`radial-gradient(circle at 82% 22%, rgba(255,255,255,${rimAlpha.toFixed(2)}) 0 9%, rgba(255,255,255,0) 36%)`,
|
|
601
|
+
`linear-gradient(135deg, rgba(255,255,255,${(0.08 + metallic * 0.16).toFixed(2)}), rgba(0,0,0,${(0.12 + roughness * 0.1).toFixed(2)}))`,
|
|
602
|
+
].join(','),
|
|
603
|
+
].join(';');
|
|
604
|
+
sphere.appendChild(shading);
|
|
605
|
+
frame.appendChild(sphere);
|
|
606
|
+
return frame;
|
|
607
|
+
}
|
|
608
|
+
function formatAssetPreviewColor(value, fallback) {
|
|
609
|
+
if (!value)
|
|
610
|
+
return fallback;
|
|
611
|
+
if (typeof value === 'string')
|
|
612
|
+
return value;
|
|
613
|
+
const r = normalizeAssetPreviewColorChannel(value.r);
|
|
614
|
+
const g = normalizeAssetPreviewColorChannel(value.g);
|
|
615
|
+
const b = normalizeAssetPreviewColorChannel(value.b);
|
|
616
|
+
if (value.a === undefined)
|
|
617
|
+
return `rgb(${r}, ${g}, ${b})`;
|
|
618
|
+
return `rgba(${r}, ${g}, ${b}, ${clampNumber(value.a, 0, 1)})`;
|
|
619
|
+
}
|
|
620
|
+
function normalizeAssetPreviewColorChannel(value) {
|
|
621
|
+
if (!Number.isFinite(value))
|
|
622
|
+
return 0;
|
|
623
|
+
return Math.round(clampNumber(value >= 0 && value <= 1 ? value * 255 : value, 0, 255));
|
|
624
|
+
}
|
|
625
|
+
function clamp01(value) {
|
|
626
|
+
return clampNumber(value, 0, 1);
|
|
627
|
+
}
|
|
628
|
+
function clampNumber(value, min, max) {
|
|
629
|
+
if (!Number.isFinite(value))
|
|
630
|
+
return min;
|
|
631
|
+
return Math.min(max, Math.max(min, value));
|
|
632
|
+
}
|
|
633
|
+
function appendAssetBrowserTabs(doc, panel, state, activeTab) {
|
|
634
|
+
const tabs = doc.createElement('div');
|
|
635
|
+
tabs.style.cssText = [
|
|
636
|
+
'display:flex',
|
|
637
|
+
'align-items:center',
|
|
638
|
+
'gap:4px',
|
|
639
|
+
'margin:0 0 8px',
|
|
640
|
+
'min-height:26px',
|
|
641
|
+
].join(';');
|
|
642
|
+
for (const tab of ASSET_BROWSER_TABS) {
|
|
643
|
+
const button = doc.createElement('button');
|
|
644
|
+
button.type = 'button';
|
|
645
|
+
button.dataset.editorAssetBrowserTab = tab.id;
|
|
646
|
+
button.title = tab.title;
|
|
647
|
+
const active = activeTab === tab.id;
|
|
648
|
+
const count = filterAssetsByBrowserTab(state.assets, tab.id).length;
|
|
649
|
+
button.textContent = `${tab.label} ${count}`;
|
|
650
|
+
button.style.cssText = [
|
|
651
|
+
'height:24px',
|
|
652
|
+
'padding:0 8px',
|
|
653
|
+
'border:1px solid var(--fps-editor-border)',
|
|
654
|
+
'border-radius:3px',
|
|
655
|
+
`background:${active ? 'var(--fps-editor-accent)' : 'var(--fps-editor-button-bg)'}`,
|
|
656
|
+
`color:${active ? 'var(--fps-editor-text-strong)' : 'var(--fps-editor-muted)'}`,
|
|
657
|
+
'font-size:11px',
|
|
658
|
+
'font-weight:800',
|
|
659
|
+
'cursor:pointer',
|
|
660
|
+
].join(';');
|
|
661
|
+
tabs.appendChild(button);
|
|
662
|
+
}
|
|
663
|
+
panel.appendChild(tabs);
|
|
664
|
+
}
|
|
665
|
+
function filterAssetsByBrowserTab(assets, tab) {
|
|
666
|
+
if (tab === 'all')
|
|
667
|
+
return [...assets];
|
|
668
|
+
return assets.filter(asset => resolveAssetBrowserTab(asset) === tab);
|
|
669
|
+
}
|
|
670
|
+
function resolveAssetBrowserTab(asset) {
|
|
671
|
+
const kind = `${asset.kind ?? ''}`.trim().toLowerCase();
|
|
672
|
+
const id = `${asset.id ?? ''}`.trim().toLowerCase();
|
|
673
|
+
const assetId = `${asset.assetId ?? ''}`.trim().toLowerCase();
|
|
674
|
+
if (kind === 'material' || kind === 'materials' || id.startsWith('material:') || assetId.startsWith('material:')) {
|
|
675
|
+
return 'materials';
|
|
676
|
+
}
|
|
677
|
+
if (kind === 'texture' || kind === 'image' || kind === 'textures' || id.startsWith('texture:') || assetId.startsWith('texture:')) {
|
|
678
|
+
return 'textures';
|
|
679
|
+
}
|
|
680
|
+
return 'models';
|
|
681
|
+
}
|
|
219
682
|
function renderHistoryPanel(doc, panel, history) {
|
|
220
683
|
const title = doc.createElement('h2');
|
|
221
684
|
const entries = history?.entries ?? [];
|
|
@@ -624,6 +1087,18 @@ function readInspectorControlOptionNumber(property, key) {
|
|
|
624
1087
|
const value = property.controlOptions?.[key];
|
|
625
1088
|
return typeof value === 'number' && Number.isFinite(value) ? value : 0;
|
|
626
1089
|
}
|
|
1090
|
+
function readInspectorAssetPickerPreview(property, key) {
|
|
1091
|
+
const value = property.controlOptions?.[key];
|
|
1092
|
+
return isLocalEditorBrowserAssetPreview(value) ? value : undefined;
|
|
1093
|
+
}
|
|
1094
|
+
function isLocalEditorBrowserAssetPreview(value) {
|
|
1095
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
1096
|
+
return false;
|
|
1097
|
+
const preview = value;
|
|
1098
|
+
if (preview.kind === 'image')
|
|
1099
|
+
return typeof preview.url === 'string' && preview.url.length > 0;
|
|
1100
|
+
return preview.kind === 'material-sphere';
|
|
1101
|
+
}
|
|
627
1102
|
function resolveInspectorVectorGroupLabel(properties, fallback) {
|
|
628
1103
|
const labels = properties.map(property => {
|
|
629
1104
|
const match = property.label.match(/^(.*)\.(x|y|z|r|g|b)$/);
|
|
@@ -683,6 +1158,20 @@ const builtinInspectorControlRegistrations = [
|
|
|
683
1158
|
control: 'color',
|
|
684
1159
|
render: ({ doc, target, property }) => createInspectorColorControl(doc, target, property),
|
|
685
1160
|
},
|
|
1161
|
+
{
|
|
1162
|
+
id: 'builtin.open-right-dock-tab',
|
|
1163
|
+
order: 90,
|
|
1164
|
+
control: 'custom',
|
|
1165
|
+
customControl: 'open-right-dock-tab',
|
|
1166
|
+
render: ({ doc, property }) => createInspectorOpenRightDockTabControl(doc, property),
|
|
1167
|
+
},
|
|
1168
|
+
{
|
|
1169
|
+
id: 'builtin.asset-picker-card',
|
|
1170
|
+
order: 95,
|
|
1171
|
+
control: 'custom',
|
|
1172
|
+
customControl: 'asset-picker-card',
|
|
1173
|
+
render: ({ doc, target, property, bindInput }) => createInspectorAssetPickerCardControl(doc, target, property, bindInput),
|
|
1174
|
+
},
|
|
686
1175
|
{
|
|
687
1176
|
id: 'builtin.asset',
|
|
688
1177
|
order: 100,
|
|
@@ -743,7 +1232,8 @@ function compareInspectorControlRegistrations(left, right) {
|
|
|
743
1232
|
return (left.order ?? 0) - (right.order ?? 0) || left.id.localeCompare(right.id);
|
|
744
1233
|
}
|
|
745
1234
|
function renderInspectorControl(doc, registrations, target, property) {
|
|
746
|
-
|
|
1235
|
+
const canRenderCustomAction = property.control === 'custom' && !!property.customControl;
|
|
1236
|
+
if (!isInspectorPropertyEditable(property) && !canRenderCustomAction)
|
|
747
1237
|
return createInspectorReadonlyControl(doc, target, property);
|
|
748
1238
|
const context = {
|
|
749
1239
|
doc,
|
|
@@ -756,6 +1246,8 @@ function renderInspectorControl(doc, registrations, target, property) {
|
|
|
756
1246
|
const registration = resolveLocalEditorBrowserInspectorControlRegistration(registrations, context);
|
|
757
1247
|
if (registration)
|
|
758
1248
|
return registration.render(context);
|
|
1249
|
+
if (!isInspectorPropertyEditable(property))
|
|
1250
|
+
return createInspectorReadonlyControl(doc, target, property);
|
|
759
1251
|
return createInspectorReadonlyControl(doc, target, property);
|
|
760
1252
|
}
|
|
761
1253
|
export function resolveLocalEditorBrowserInspectorControlRegistration(registrations, context) {
|
|
@@ -1024,6 +1516,127 @@ function createInspectorColorControl(doc, target, property) {
|
|
|
1024
1516
|
input.style.cssText = 'width:34px;height:26px;border:1px solid var(--fps-editor-editable-border);border-radius:3px;background:var(--fps-editor-editable-bg);padding:0;box-shadow:var(--fps-editor-editable-shadow)';
|
|
1025
1517
|
return input;
|
|
1026
1518
|
}
|
|
1519
|
+
function createInspectorOpenRightDockTabControl(doc, property) {
|
|
1520
|
+
const targetTabOption = readInspectorControlOptionString(property, 'targetTab');
|
|
1521
|
+
const targetTab = targetTabOption === 'inspector' ? 'inspector' : 'rendering';
|
|
1522
|
+
const label = readInspectorControlOptionString(property, 'label')
|
|
1523
|
+
|| (typeof property.value === 'string' ? property.value : property.label);
|
|
1524
|
+
const button = createToolbarButton(doc, label, RIGHT_DOCK_TAB_ICONS[targetTab]);
|
|
1525
|
+
button.type = 'button';
|
|
1526
|
+
button.dataset.editorOpenRightDockTab = targetTab;
|
|
1527
|
+
button.title = property.tooltip ?? label;
|
|
1528
|
+
button.dataset.editorTooltip = button.title;
|
|
1529
|
+
button.style.cssText = [
|
|
1530
|
+
button.style.cssText,
|
|
1531
|
+
'width:100%',
|
|
1532
|
+
'justify-content:center',
|
|
1533
|
+
'height:28px',
|
|
1534
|
+
].filter(Boolean).join(';');
|
|
1535
|
+
return button;
|
|
1536
|
+
}
|
|
1537
|
+
function createInspectorAssetPickerCardControl(doc, _target, property, bindInput) {
|
|
1538
|
+
const wrapper = doc.createElement('div');
|
|
1539
|
+
wrapper.dataset.editorInspectorAssetPickerControl = 'true';
|
|
1540
|
+
wrapper.style.cssText = [
|
|
1541
|
+
'min-width:0',
|
|
1542
|
+
'display:grid',
|
|
1543
|
+
readInspectorControlOptionString(property, 'copyActionValue') ? 'grid-template-columns:1fr auto auto' : 'grid-template-columns:1fr auto',
|
|
1544
|
+
'align-items:stretch',
|
|
1545
|
+
'gap:6px',
|
|
1546
|
+
].join(';');
|
|
1547
|
+
const input = doc.createElement('input');
|
|
1548
|
+
input.type = 'hidden';
|
|
1549
|
+
input.value = property.mixed ? '' : String(property.value ?? '');
|
|
1550
|
+
bindInput(input, { source: 'asset' });
|
|
1551
|
+
wrapper.appendChild(input);
|
|
1552
|
+
const card = doc.createElement('button');
|
|
1553
|
+
card.type = 'button';
|
|
1554
|
+
card.dataset.editorInspectorAssetPicker = 'true';
|
|
1555
|
+
card.disabled = !isInspectorPropertyEditable(property);
|
|
1556
|
+
card.style.cssText = [
|
|
1557
|
+
'min-width:0',
|
|
1558
|
+
'min-height:42px',
|
|
1559
|
+
'display:flex',
|
|
1560
|
+
'align-items:center',
|
|
1561
|
+
'gap:8px',
|
|
1562
|
+
'padding:6px 7px',
|
|
1563
|
+
'border:1px solid var(--fps-editor-editable-border)',
|
|
1564
|
+
'border-radius:3px',
|
|
1565
|
+
'background:var(--fps-editor-field)',
|
|
1566
|
+
'color:var(--fps-editor-text)',
|
|
1567
|
+
'text-align:left',
|
|
1568
|
+
card.disabled ? 'cursor:not-allowed' : 'cursor:pointer',
|
|
1569
|
+
card.disabled ? 'opacity:0.72' : '',
|
|
1570
|
+
].filter(Boolean).join(';');
|
|
1571
|
+
const label = readInspectorControlOptionString(property, 'currentLabel')
|
|
1572
|
+
|| readInspectorControlOptionString(property, 'placeholder')
|
|
1573
|
+
|| (property.mixed ? '--' : String(property.value || 'None'));
|
|
1574
|
+
const meta = readInspectorControlOptionString(property, 'currentMeta')
|
|
1575
|
+
|| readInspectorControlOptionString(property, 'pickerKind')
|
|
1576
|
+
|| property.label;
|
|
1577
|
+
const preview = readInspectorAssetPickerPreview(property, 'currentPreview');
|
|
1578
|
+
card.appendChild(createAssetPreviewElement(doc, {
|
|
1579
|
+
id: `${property.path}:preview`,
|
|
1580
|
+
label,
|
|
1581
|
+
preview,
|
|
1582
|
+
}, 'list'));
|
|
1583
|
+
const body = doc.createElement('div');
|
|
1584
|
+
body.style.cssText = 'min-width:0;flex:1';
|
|
1585
|
+
const title = doc.createElement('div');
|
|
1586
|
+
title.textContent = label;
|
|
1587
|
+
title.style.cssText = 'font-weight:900;overflow:hidden;text-overflow:ellipsis;white-space:nowrap';
|
|
1588
|
+
const subtitle = doc.createElement('div');
|
|
1589
|
+
subtitle.textContent = meta;
|
|
1590
|
+
subtitle.style.cssText = 'color:var(--fps-editor-muted);font-size:11px;font-weight:800;overflow:hidden;text-overflow:ellipsis;white-space:nowrap';
|
|
1591
|
+
body.appendChild(title);
|
|
1592
|
+
body.appendChild(subtitle);
|
|
1593
|
+
card.appendChild(body);
|
|
1594
|
+
wrapper.appendChild(card);
|
|
1595
|
+
const action = doc.createElement('button');
|
|
1596
|
+
action.type = 'button';
|
|
1597
|
+
action.dataset.editorInspectorAssetPicker = 'true';
|
|
1598
|
+
action.disabled = card.disabled;
|
|
1599
|
+
action.textContent = readInspectorControlOptionString(property, 'actionLabel') || '替换';
|
|
1600
|
+
action.style.cssText = [
|
|
1601
|
+
'width:54px',
|
|
1602
|
+
'min-height:42px',
|
|
1603
|
+
'padding:0 8px',
|
|
1604
|
+
'border:1px solid var(--fps-editor-editable-border)',
|
|
1605
|
+
'border-radius:3px',
|
|
1606
|
+
'background:var(--fps-editor-button-bg)',
|
|
1607
|
+
'color:var(--fps-editor-text)',
|
|
1608
|
+
'font-size:11px',
|
|
1609
|
+
'font-weight:900',
|
|
1610
|
+
action.disabled ? 'cursor:not-allowed' : 'cursor:pointer',
|
|
1611
|
+
action.disabled ? 'opacity:0.72' : '',
|
|
1612
|
+
].filter(Boolean).join(';');
|
|
1613
|
+
wrapper.appendChild(action);
|
|
1614
|
+
const copyValue = readInspectorControlOptionString(property, 'copyActionValue');
|
|
1615
|
+
if (copyValue) {
|
|
1616
|
+
const copyAction = doc.createElement('button');
|
|
1617
|
+
copyAction.type = 'button';
|
|
1618
|
+
copyAction.dataset.editorInspectorAssetCopy = 'true';
|
|
1619
|
+
copyAction.dataset.editorInspectorAssetCopyValue = copyValue;
|
|
1620
|
+
copyAction.disabled = card.disabled;
|
|
1621
|
+
copyAction.textContent = readInspectorControlOptionString(property, 'copyActionLabel') || '复制';
|
|
1622
|
+
copyAction.title = readInspectorControlOptionString(property, 'copyActionTooltip');
|
|
1623
|
+
copyAction.style.cssText = [
|
|
1624
|
+
'width:48px',
|
|
1625
|
+
'min-height:42px',
|
|
1626
|
+
'padding:0 8px',
|
|
1627
|
+
'border:1px solid var(--fps-editor-editable-border)',
|
|
1628
|
+
'border-radius:3px',
|
|
1629
|
+
'background:var(--fps-editor-button-bg)',
|
|
1630
|
+
'color:var(--fps-editor-text)',
|
|
1631
|
+
'font-size:11px',
|
|
1632
|
+
'font-weight:900',
|
|
1633
|
+
copyAction.disabled ? 'cursor:not-allowed' : 'cursor:pointer',
|
|
1634
|
+
copyAction.disabled ? 'opacity:0.72' : '',
|
|
1635
|
+
].filter(Boolean).join(';');
|
|
1636
|
+
wrapper.appendChild(copyAction);
|
|
1637
|
+
}
|
|
1638
|
+
return wrapper;
|
|
1639
|
+
}
|
|
1027
1640
|
function createInspectorReadonlyControl(doc, _target, property) {
|
|
1028
1641
|
if (!property.mixed && isExpandableInspectorValue(property.value)) {
|
|
1029
1642
|
return createInspectorObjectReadonlyControl(doc, property);
|
|
@@ -1218,6 +1831,18 @@ export function formatLocalEditorBrowserInspectorNumberValue(value) {
|
|
|
1218
1831
|
return '0';
|
|
1219
1832
|
return rounded.toFixed(3).replace(/\.?0+$/, '');
|
|
1220
1833
|
}
|
|
1834
|
+
export function parseLocalEditorBrowserInspectorNumberValue(value, mode) {
|
|
1835
|
+
const trimmed = value.trim();
|
|
1836
|
+
if (trimmed === '')
|
|
1837
|
+
return mode === 'final' ? 0 : null;
|
|
1838
|
+
if (mode === 'live' && !isCompleteInspectorNumberInput(trimmed))
|
|
1839
|
+
return null;
|
|
1840
|
+
const numeric = Number(trimmed);
|
|
1841
|
+
return Number.isFinite(numeric) ? numeric : null;
|
|
1842
|
+
}
|
|
1843
|
+
function isCompleteInspectorNumberInput(value) {
|
|
1844
|
+
return /^[+-]?(?:\d+|\d*\.\d+)(?:[eE][+-]?\d+)?$/.test(value);
|
|
1845
|
+
}
|
|
1221
1846
|
function formatInspectorEditableValue(value, property) {
|
|
1222
1847
|
if (typeof value === 'number'
|
|
1223
1848
|
&& (property.control === 'number' || property.valueType === 'number' || property.control === 'vec2' || property.control === 'vec3')) {
|