feat(scene): shared zoom/pan for control and presentation

Keep effects pinned to map coordinates when the viewport changes; materials/NPC overlays stay screen-fixed.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ivan Fontosh
2026-07-23 14:08:19 +08:00
parent 2c0e6fbf09
commit eb127c11c2
13 changed files with 688 additions and 19 deletions
+26
View File
@@ -26,6 +26,7 @@ import { LicenseService } from './license/licenseService';
import { MaterialsOverlayStore } from './materials/materialsOverlayStore';
import { NpcsOverlayStore } from './npcs/npcsOverlayStore';
import { ZipProjectStore } from './project/zipStore';
import { SceneViewStore } from './sceneView/sceneViewStore';
import { registerDndAssetProtocol } from './protocol/dndAssetProtocol';
import { installAutoUpdater } from './update/installAutoUpdater';
import { getAppSemanticVersion, getOptionalBuildNumber } from './versionInfo';
@@ -146,6 +147,7 @@ function installAppMenuForSession(): void {
const effectsStore = new EffectsStore();
const sceneDarknessStore = new SceneDarknessStore();
const sceneViewStore = new SceneViewStore();
const videoStore = new VideoPlaybackStore();
const materialsOverlayStore = new MaterialsOverlayStore();
const npcsOverlayStore = new NpcsOverlayStore();
@@ -196,6 +198,13 @@ function emitSceneDarknessState(): void {
}
}
function emitSceneViewState(): void {
const state = sceneViewStore.getState();
for (const win of BrowserWindow.getAllWindows()) {
win.webContents.send(ipcChannels.sceneView.stateChanged, { state });
}
}
function syncSceneDarknessForProject(project: Project): void {
const cacheKey = project.currentGraphNodeId ?? project.currentSceneId ?? null;
const scene = project.currentSceneId ? project.scenes[project.currentSceneId] : undefined;
@@ -438,6 +447,8 @@ async function main() {
});
registerHandler(ipcChannels.project.open, async ({ projectId }) => {
const project = await projectStore.openProjectById(projectId);
sceneViewStore.reset();
emitSceneViewState();
emitSessionState();
return { project };
});
@@ -447,10 +458,12 @@ async function main() {
materialsOverlayStore.clear();
npcsOverlayStore.clear();
sceneDarknessStore.resetSession();
sceneViewStore.reset();
emitEffectsState();
emitMaterialsOverlayState();
emitNpcsOverlayState();
emitSceneDarknessState();
emitSceneViewState();
emitSessionState();
return { ok: true };
});
@@ -466,12 +479,14 @@ async function main() {
effectsStore.clear();
materialsOverlayStore.clear();
npcsOverlayStore.clear();
sceneViewStore.reset();
const project = projectStore.getOpenProject();
if (project) syncSceneDarknessForProject(project);
emitEffectsState();
emitMaterialsOverlayState();
emitNpcsOverlayState();
emitSceneDarknessState();
emitSceneViewState();
emitSessionState();
return { currentSceneId: projectStore.getOpenProject()?.currentSceneId ?? null };
});
@@ -487,12 +502,14 @@ async function main() {
effectsStore.clear();
materialsOverlayStore.clear();
npcsOverlayStore.clear();
sceneViewStore.reset();
const project = projectStore.getOpenProject();
if (project) syncSceneDarknessForProject(project);
emitEffectsState();
emitMaterialsOverlayState();
emitNpcsOverlayState();
emitSceneDarknessState();
emitSceneViewState();
emitSessionState();
const p = projectStore.getOpenProject();
return {
@@ -1082,6 +1099,15 @@ async function main() {
return { ok: true };
});
registerHandler(ipcChannels.sceneView.getState, () => {
return { state: sceneViewStore.getState() };
});
registerHandler(ipcChannels.sceneView.dispatch, ({ event }) => {
sceneViewStore.dispatch(event);
emitSceneViewState();
return { ok: true };
});
registerHandler(ipcChannels.video.getState, () => {
return { state: videoStore.getState() };
});