WIP: Handle deletion from in-game editor

This commit is contained in:
Florian Rival
2025-01-23 19:55:01 +01:00
parent 918f8ec989
commit ca3836b3c7
5 changed files with 146 additions and 69 deletions

View File

@@ -4,6 +4,8 @@ namespace gdjs {
const RIGHT_KEY = 39;
const DOWN_KEY = 40;
const ALT_KEY = 18;
const DEL_KEY = 46;
const BACKSPACE_KEY = 8;
const LEFT_ALT_KEY = gdjs.InputManager.getLocationAwareKeyCode(ALT_KEY, 1);
const RIGHT_ALT_KEY = gdjs.InputManager.getLocationAwareKeyCode(ALT_KEY, 2);
const SHIFT_KEY = 16;
@@ -57,6 +59,13 @@ namespace gdjs {
);
};
const shouldDeleteSelection = (inputManager: gdjs.InputManager) => {
return (
inputManager.isKeyPressed(DEL_KEY) ||
inputManager.isKeyPressed(BACKSPACE_KEY)
);
};
const shouldScrollHorizontally = isAltPressed;
const shouldZoom = (inputManager: gdjs.InputManager) => {
@@ -90,6 +99,7 @@ namespace gdjs {
} | null = null;
private _lastCursorX: number = 0;
private _lastCursorY: number = 0;
private _wasManipulatingSelectionLastFrame = false;
private _isManipulatingSelection = false;
private _selectedObjects: Array<gdjs.RuntimeObject> = [];
@@ -175,7 +185,17 @@ namespace gdjs {
}: {
objectUnderCursor: ObjectUnderCursor | null;
}) {
const currentScene = this._runtimeGame.getSceneStack().getCurrentScene();
const inputManager = this._runtimeGame.getInputManager();
if (!currentScene) return;
if (
this._wasManipulatingSelectionLastFrame &&
!this._isManipulatingSelection
) {
// Just finished dragging/editing the selection.
this._sendSelectionUpdate();
}
// Left click: select the object under the cursor.
if (
@@ -193,6 +213,19 @@ namespace gdjs {
}
}
}
if (shouldDeleteSelection(inputManager)) {
const removedObjects = this._selectedObjects;
removedObjects.forEach((object) => {
object.deleteFromScene(currentScene);
});
this._selectedObjects = [];
this._sendSelectionUpdate({
removedObjects,
});
}
this._wasManipulatingSelectionLastFrame = this._isManipulatingSelection;
}
private _updateSelectionOutline({
@@ -289,8 +322,6 @@ namespace gdjs {
return;
}
this._isManipulatingSelection = false;
this._sendSelectionUpdate();
});
this._currentTransformControls = {
@@ -312,23 +343,25 @@ namespace gdjs {
}
}
private _sendSelectionUpdate() {
private _sendSelectionUpdate(options?: {
removedObjects: Array<gdjs.RuntimeObject>;
}) {
const debuggerClient = this._runtimeGame._debuggerClient;
if (!debuggerClient) return;
const instancesSelection = this._selectedObjects
const getPersistentUuidsFromObjects = (
objects: Array<gdjs.RuntimeObject>
): Array<InstancePersistentUuidData> =>
objects
.map((object) => {
if (!object.persistentUuid) return null;
return { persistentUuid: object.persistentUuid };
})
.filter(isDefined);
const updatedInstances = this._selectedObjects
.map((object) => {
if (!object.persistentUuid) return null;
return { persistentUuid: object.persistentUuid };
})
.filter(isDefined);
const instanceUpdates = this._selectedObjects
.map((object) => {
const rendererObject = object.getRendererObject();
if (!rendererObject) return null;
if (object instanceof gdjs.RuntimeObject3D) {
if (!object.persistentUuid) return null;
@@ -353,9 +386,12 @@ namespace gdjs {
})
.filter(isDefined);
debuggerClient.sendInstancesUpdated({
instanceUpdates,
instancesSelection,
debuggerClient.sendInstanceChanges({
updatedInstances,
selectedInstances: getPersistentUuidsFromObjects(this._selectedObjects),
removedInstances: options
? getPersistentUuidsFromObjects(options.removedObjects)
: [],
});
}

View File

@@ -637,16 +637,15 @@ namespace gdjs {
);
}
sendInstancesUpdated(update: {
instanceUpdates: Array<InstanceData>;
instancesSelection: Array<{
persistentUuid: string;
}>;
sendInstanceChanges(changes: {
updatedInstances: Array<InstanceData>;
selectedInstances: Array<InstancePersistentUuidData>;
removedInstances: Array<InstancePersistentUuidData>;
}): void {
this._sendMessage(
circularSafeStringify({
command: 'updateInstances',
payload: update,
payload: changes,
})
);
}

View File

@@ -238,9 +238,11 @@ declare interface ExternalLayoutData {
instances: InstanceData[];
}
declare interface InstanceData {
declare interface InstancePersistentUuidData {
persistentUuid: string;
}
declare interface InstanceData extends InstancePersistentUuidData {
layer: string;
locked: boolean;
name: string;

View File

@@ -100,17 +100,19 @@ export const localPreviewDebuggerServer: PreviewDebuggerServer = {
ipcRenderer.on('debugger-message-received', (event, { id, message }) => {
console.info('Processing message received for debugger');
let parsedMessage = null;
try {
const parsedMessage = JSON.parse(message);
callbacksList.forEach(({ onHandleParsedMessage }) =>
onHandleParsedMessage({ id, parsedMessage })
);
parsedMessage = JSON.parse(message);
} catch (e) {
console.warn(
'Error while parsing message received from debugger client:',
e
);
}
callbacksList.forEach(({ onHandleParsedMessage }) =>
onHandleParsedMessage({ id, parsedMessage })
);
});
ipcRenderer.send('debugger-start-server');
});

View File

@@ -90,6 +90,16 @@ const gd: libGDevelop = global.gd;
const BASE_LAYER_NAME = '';
const INSTANCES_CLIPBOARD_KIND = 'Instances';
type InstancePersistentUuidData = {|
persistentUuid: string,
|};
type InstanceChanges = {|
updatedInstances: Array<any>, // TODO
selectedInstances: Array<InstancePersistentUuidData>,
removedInstances: Array<InstancePersistentUuidData>,
|};
export type EditorId =
| 'objects-list'
| 'properties'
@@ -263,7 +273,11 @@ export default class SceneEditor extends React.Component<Props, State> {
onConnectionOpened: () => {},
onConnectionErrored: () => {},
onServerStateChanged: () => {},
onHandleParsedMessage: this.onReceiveMessageFromGame.bind(this),
onHandleParsedMessage: ({ id, parsedMessage }) => {
if (parsedMessage.command === 'updateInstances') {
this.onReceiveInstanceChanges(parsedMessage.payload);
}
},
}
);
}
@@ -281,54 +295,78 @@ export default class SceneEditor extends React.Component<Props, State> {
return this.state.instancesEditorSettings;
}
onReceiveMessageFromGame({
id,
parsedMessage,
}: {
id: number,
parsedMessage: {| command: string, payload: any |},
}) {
onReceiveInstanceChanges(changes: InstanceChanges) {
// TODO: ensure this works for external layouts too.
if (parsedMessage.command === 'updateInstances') {
// TODO: adapt this to get all instances in one shot.
const modifiedInstances: gdInitialInstance[] = [];
parsedMessage.payload.instanceUpdates.forEach(instanceData => {
const { persistentUuid, x, y, z } = instanceData;
// TODO: adapt all of this to get all instances in one shot.
const modifiedInstances: gdInitialInstance[] = [];
changes.updatedInstances.forEach(instanceData => {
const { persistentUuid, x, y, z } = instanceData;
const instance = getInstanceInLayoutWithPersistentUuid(
this.props.initialInstances,
persistentUuid
);
if (!instance) return;
instance.setX(x);
instance.setY(y);
instance.setZ(z);
modifiedInstances.push(instance);
});
this._onInstancesMoved(modifiedInstances);
const newlySelectedInstances = changes.selectedInstances
.map(selectedInstanceData => {
const { persistentUuid } = selectedInstanceData;
const instance = getInstanceInLayoutWithPersistentUuid(
this.props.initialInstances,
persistentUuid
);
if (!instance) return;
instance.setX(x);
instance.setY(y);
instance.setZ(z);
return instance || null;
})
.filter(Boolean);
modifiedInstances.push(instance);
});
this._onInstancesMoved(modifiedInstances);
const justRemovedInstances = changes.removedInstances
.map(removedInstanceData => {
const { persistentUuid } = removedInstanceData;
const instance = getInstanceInLayoutWithPersistentUuid(
this.props.initialInstances,
persistentUuid
);
return instance || null;
})
.filter(Boolean);
console.log("payload", parsedMessage.payload);
const newlySelectedInstances = parsedMessage.payload.instancesSelection
.map(instanceSelectionData => {
const { persistentUuid } = instanceSelectionData;
console.log('selecting', persistentUuid);
const instance = getInstanceInLayoutWithPersistentUuid(
justRemovedInstances.forEach(instance => {
this.props.initialInstances.removeInstance(instance);
});
if (justRemovedInstances.length) {
this.setState(
{
selectedObjectFolderOrObjectsWithContext: [],
history: saveToHistory(
this.state.history,
this.props.initialInstances,
persistentUuid
);
return instance || null;
})
.filter(Boolean);
this.instancesSelection.selectInstances({
instances: newlySelectedInstances,
multiSelect: false,
layersLocks: null,
ignoreSeal: true
});
this.setState({ lastSelectionType: 'instance' });
this.updateToolbar();
'DELETE'
),
},
() => {
this.updateToolbar();
this.forceUpdatePropertiesEditor();
}
);
}
console.log('Selecting', newlySelectedInstances.length);
console.log("Changes:", changes)
this.instancesSelection.selectInstances({
instances: newlySelectedInstances,
multiSelect: false,
layersLocks: null,
ignoreSeal: true,
});
this.setState({ lastSelectionType: 'instance' });
this.updateToolbar();
}
onResourceExternallyChanged = async (resourceInfo: {|