feat: scene traps, material legends, and scene editor window

Add trap overlays with session state, material legend editor/panel, and a dedicated scene editor window wired through IPC and project persistence.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ivan Fontosh
2026-07-24 16:29:43 +08:00
parent d3b1c4660d
commit 02d73ddf81
39 changed files with 2563 additions and 36 deletions
+53
View File
@@ -17,6 +17,7 @@ export type WindowKind =
| 'sceneDescription'
| 'materials'
| 'npcsEditor'
| 'sceneEditor'
| 'npcs';
/** Окна, которые реально слушают session.stateChanged (редактор синхронизируется через invoke). */
@@ -26,6 +27,7 @@ export const SESSION_STATE_WINDOW_KINDS: readonly WindowKind[] = [
'materials',
'npcs',
'npcsEditor',
'sceneEditor',
] as const;
const windows = new Map<WindowKind, BrowserWindow>();
@@ -122,6 +124,8 @@ function pageNameForKind(kind: WindowKind): string {
return 'materials.html';
case 'npcsEditor':
return 'npcsEditor.html';
case 'sceneEditor':
return 'sceneEditor.html';
case 'npcs':
return 'npcs.html';
}
@@ -192,6 +196,7 @@ function windowSizeForKind(kind: WindowKind): { width: number; height: number }
if (kind === 'sceneDescription') return { width: 720, height: 640 };
if (kind === 'materials') return { width: MATERIALS_WINDOW_WIDTH, height: MATERIALS_WINDOW_HEIGHT };
if (kind === 'npcsEditor') return { width: NPCS_EDITOR_WINDOW_WIDTH, height: NPCS_EDITOR_WINDOW_HEIGHT };
if (kind === 'sceneEditor') return { width: 1280, height: 800 };
if (kind === 'npcs') return { width: NPCS_WINDOW_WIDTH, height: NPCS_WINDOW_HEIGHT };
return { width: 1280, height: 800 };
}
@@ -229,6 +234,15 @@ function createWindow(kind: WindowKind, opts?: CreateWindowOpts): BrowserWindow
autoHideMenuBar: true,
}
: {}),
...(kind === 'sceneEditor'
? {
width: 1280,
height: 800,
minWidth: 960,
minHeight: 600,
autoHideMenuBar: true,
}
: {}),
...(kind === 'npcs'
? {
width: NPCS_WINDOW_WIDTH,
@@ -267,6 +281,7 @@ function createWindow(kind: WindowKind, opts?: CreateWindowOpts): BrowserWindow
kind === 'sceneDescription' ||
kind === 'materials' ||
kind === 'npcsEditor' ||
kind === 'sceneEditor' ||
kind === 'npcs'
) {
win.setMenuBarVisibility(false);
@@ -421,6 +436,13 @@ export function closeNpcsEditorWindow(): void {
}
}
export function closeSceneEditorWindow(): void {
const win = windows.get('sceneEditor');
if (win && !win.isDestroyed()) {
win.close();
}
}
export function closeNpcsWindow(): void {
const win = windows.get('npcs');
if (win && !win.isDestroyed()) {
@@ -536,6 +558,37 @@ export function openNpcsEditorWindow(): void {
});
}
/** Редактор сцены: расстановка ловушек и легенды материалов. */
export function openSceneEditorWindow(): void {
const existing = windows.get('sceneEditor');
if (existing && !existing.isDestroyed()) {
if (existing.isMinimized()) existing.restore();
existing.show();
existing.focus();
existing.moveTop();
return;
}
const parent = windows.get('editor');
const win = createWindow('sceneEditor', parent ? { parent } : undefined);
const { width, height } = win.getBounds();
const display = screen.getDisplayMatching(parent?.getBounds() ?? win.getBounds());
const { x, y, width: dw, height: dh } = display.workArea;
win.setBounds({
x: Math.round(x + Math.max(0, (dw - width) / 2)),
y: Math.round(y + Math.max(0, (dh - height) / 2)),
width,
height,
});
win.webContents.once('did-finish-load', () => {
if (!win.isDestroyed()) {
win.show();
win.focus();
win.moveTop();
}
});
}
/** Пульт НПС: список + описание выбранного; оверлей аватара на сцене. */
export function openNpcsWindow(): void {
const existing = windows.get('npcs');