Skip to content

Commit e10dd57

Browse files
authored
In the scene editor, paste cut instances at highest z order (#4726)
1 parent 8932574 commit e10dd57

File tree

4 files changed

+185
-75
lines changed

4 files changed

+185
-75
lines changed

newIDE/app/src/InstancesEditor/InstancesAdder.js

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// @flow
22
import { roundPosition } from '../Utils/GridHelpers';
3+
import { unserializeFromJSObject } from '../Utils/Serializer';
34
import { type InstancesEditorSettings } from './InstancesEditorSettings';
45
const gd: libGDevelop = global.gd;
56

@@ -51,6 +52,67 @@ export default class InstancesAdder {
5152
this._instancesEditorSettings = instancesEditorSettings;
5253
}
5354

55+
addSerializedInstances = ({
56+
position,
57+
copyReferential,
58+
serializedInstances,
59+
preventSnapToGrid = false,
60+
addInstancesInTheForeground = false,
61+
}: {|
62+
position: [number, number],
63+
copyReferential: [number, number],
64+
serializedInstances: Array<Object>,
65+
preventSnapToGrid?: boolean,
66+
addInstancesInTheForeground?: boolean,
67+
|}): Array<gdInitialInstance> => {
68+
this._zOrderFinder.reset();
69+
this._instances.iterateOverInstances(this._zOrderFinder);
70+
const sceneForegroundZOrder = this._zOrderFinder.getHighestZOrder() + 1;
71+
72+
let addedInstancesLowestZOrder = null;
73+
74+
const newInstances = serializedInstances.map(serializedInstance => {
75+
const instance = new gd.InitialInstance();
76+
unserializeFromJSObject(instance, serializedInstance);
77+
const desiredPosition = [
78+
instance.getX() - copyReferential[0] + position[0],
79+
instance.getY() - copyReferential[1] + position[1],
80+
];
81+
const newPos = preventSnapToGrid
82+
? desiredPosition
83+
: roundPositionsToGrid(desiredPosition, this._instancesEditorSettings);
84+
instance.setX(newPos[0]);
85+
instance.setY(newPos[1]);
86+
if (addInstancesInTheForeground) {
87+
if (
88+
addedInstancesLowestZOrder === null ||
89+
addedInstancesLowestZOrder > instance.getZOrder()
90+
) {
91+
addedInstancesLowestZOrder = instance.getZOrder();
92+
}
93+
}
94+
const newInstance = this._instances
95+
.insertInitialInstance(instance)
96+
.resetPersistentUuid();
97+
instance.delete();
98+
return newInstance;
99+
});
100+
101+
if (addInstancesInTheForeground && addedInstancesLowestZOrder !== null) {
102+
newInstances.forEach(instance => {
103+
instance.setZOrder(
104+
instance.getZOrder() -
105+
// Flow is not happy with addedInstancesLowestZOrder possible null value
106+
// so 0 is used as a fallback.
107+
(addedInstancesLowestZOrder || 0) +
108+
sceneForegroundZOrder
109+
);
110+
});
111+
}
112+
113+
return newInstances;
114+
};
115+
54116
/**
55117
* Immediately create new instance at the specified position
56118
* (specified in scene coordinates).

newIDE/app/src/InstancesEditor/index.js

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -549,12 +549,26 @@ export default class InstancesEditor extends Component<Props> {
549549
};
550550

551551
/**
552-
* Immediately add instances for the specified objects at the given
552+
* Immediately add serialized instances at the given
553553
* position (in scene coordinates).
554554
*/
555+
addSerializedInstances = (options: {|
556+
position: [number, number],
557+
copyReferential: [number, number],
558+
serializedInstances: Array<Object>,
559+
preventSnapToGrid?: boolean,
560+
addInstancesInTheForeground?: boolean,
561+
|}): Array<gdInitialInstance> => {
562+
return this._instancesAdder.addSerializedInstances(options);
563+
};
564+
565+
/**
566+
* Immediately add instances for the specified objects at the given
567+
* position (in scene coordinates) given their names.
568+
*/
555569
addInstances = (
556-
pos /*: [number, number] */,
557-
objectNames /*: Array<string> */
570+
pos: [number, number],
571+
objectNames: Array<string>
558572
): Array<gdInitialInstance> => {
559573
return this._instancesAdder.addInstances(pos, objectNames);
560574
};

newIDE/app/src/ObjectsList/index.js

Lines changed: 72 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -316,28 +316,22 @@ const ObjectsList = React.forwardRef<Props, ObjectsListInterface>(
316316
[copyObject, deleteObject]
317317
);
318318

319-
const paste = React.useCallback(
320-
(objectWithContext: ObjectWithContext): ?ObjectWithContext => {
321-
if (!Clipboard.has(CLIPBOARD_KIND)) return null;
322-
323-
const { object: pasteObject, global } = objectWithContext;
324-
const clipboardContent = Clipboard.get(CLIPBOARD_KIND);
325-
const copiedObject = SafeExtractor.extractObjectProperty(
326-
clipboardContent,
327-
'object'
328-
);
329-
const name = SafeExtractor.extractStringProperty(
330-
clipboardContent,
331-
'name'
332-
);
333-
const type = SafeExtractor.extractStringProperty(
334-
clipboardContent,
335-
'type'
336-
);
337-
if (!name || !type || !copiedObject) return;
338-
319+
const addSerializedObjectToObjectsContainer = React.useCallback(
320+
({
321+
objectName,
322+
positionObjectName,
323+
objectType,
324+
global,
325+
serializedObject,
326+
}: {|
327+
objectName: string,
328+
positionObjectName: string,
329+
objectType: string,
330+
global: boolean,
331+
serializedObject: Object,
332+
|}): ObjectWithContext => {
339333
const newName = newNameGenerator(
340-
name,
334+
objectName,
341335
name =>
342336
objectsContainer.hasObjectNamed(name) ||
343337
project.hasObjectNamed(name),
@@ -347,31 +341,64 @@ const ObjectsList = React.forwardRef<Props, ObjectsListInterface>(
347341
const newObject = global
348342
? project.insertNewObject(
349343
project,
350-
type,
344+
objectType,
351345
newName,
352-
project.getObjectPosition(pasteObject.getName()) + 1
346+
project.getObjectPosition(positionObjectName) + 1
353347
)
354348
: objectsContainer.insertNewObject(
355349
project,
356-
type,
350+
objectType,
357351
newName,
358-
objectsContainer.getObjectPosition(pasteObject.getName()) + 1
352+
objectsContainer.getObjectPosition(positionObjectName) + 1
359353
);
360354

361355
unserializeFromJSObject(
362356
newObject,
363-
copiedObject,
357+
serializedObject,
364358
'unserializeFrom',
365359
project
366360
);
367361
newObject.setName(newName); // Unserialization has overwritten the name.
368362

363+
return { object: newObject, global };
364+
},
365+
[objectsContainer, project]
366+
);
367+
368+
const paste = React.useCallback(
369+
(objectWithContext: ObjectWithContext): ?ObjectWithContext => {
370+
if (!Clipboard.has(CLIPBOARD_KIND)) return null;
371+
372+
const { object: pasteObject, global } = objectWithContext;
373+
const clipboardContent = Clipboard.get(CLIPBOARD_KIND);
374+
const copiedObject = SafeExtractor.extractObjectProperty(
375+
clipboardContent,
376+
'object'
377+
);
378+
const name = SafeExtractor.extractStringProperty(
379+
clipboardContent,
380+
'name'
381+
);
382+
const type = SafeExtractor.extractStringProperty(
383+
clipboardContent,
384+
'type'
385+
);
386+
if (!name || !type || !copiedObject) return;
387+
388+
const newObjectWithContext = addSerializedObjectToObjectsContainer({
389+
objectName: name,
390+
positionObjectName: pasteObject.getName(),
391+
objectType: type,
392+
serializedObject: copiedObject,
393+
global,
394+
});
395+
369396
onObjectModified(false);
370-
if (onObjectPasted) onObjectPasted(newObject);
397+
if (onObjectPasted) onObjectPasted(newObjectWithContext.object);
371398

372-
return { object: newObject, global };
399+
return newObjectWithContext;
373400
},
374-
[objectsContainer, onObjectModified, onObjectPasted, project]
401+
[addSerializedObjectToObjectsContainer, onObjectModified, onObjectPasted]
375402
);
376403

377404
const editName = React.useCallback(
@@ -383,19 +410,25 @@ const ObjectsList = React.forwardRef<Props, ObjectsListInterface>(
383410
[]
384411
);
385412

386-
const pasteAndRename = React.useCallback(
387-
(objectWithContext: ObjectWithContext) => {
388-
editName(paste(objectWithContext));
389-
},
390-
[editName, paste]
391-
);
392-
393413
const duplicateObject = React.useCallback(
394414
(objectWithContext: ObjectWithContext) => {
395-
copyObject(objectWithContext);
396-
pasteAndRename(objectWithContext);
415+
const { object, global } = objectWithContext;
416+
417+
const type = object.getType();
418+
const name = object.getName();
419+
const serializedObject = serializeToJSObject(object);
420+
421+
const newObjectWithContext = addSerializedObjectToObjectsContainer({
422+
objectName: name,
423+
positionObjectName: name,
424+
objectType: type,
425+
serializedObject,
426+
global,
427+
});
428+
429+
editName(newObjectWithContext);
397430
},
398-
[copyObject, pasteAndRename]
431+
[addSerializedObjectToObjectsContainer, editName]
399432
);
400433

401434
const rename = React.useCallback(

newIDE/app/src/SceneEditor/index.js

Lines changed: 34 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,7 @@ import SetupGridDialog from './SetupGridDialog';
2424
import ScenePropertiesDialog from './ScenePropertiesDialog';
2525
import { type ObjectEditorTab } from '../ObjectEditor/ObjectEditorDialog';
2626
import Toolbar from './Toolbar';
27-
import {
28-
serializeToJSObject,
29-
unserializeFromJSObject,
30-
} from '../Utils/Serializer';
27+
import { serializeToJSObject } from '../Utils/Serializer';
3128
import Clipboard, { SafeExtractor } from '../Utils/Clipboard';
3229
import Window from '../Utils/Window';
3330
import FullSizeInstancesEditorWithScrollbars from '../InstancesEditor/FullSizeInstancesEditorWithScrollbars';
@@ -157,7 +154,10 @@ type State = {|
157154
selectedObjectTags: SelectedTags,
158155
|};
159156

160-
type CopyCutPasteOptions = { useLastCursorPosition?: boolean };
157+
type CopyCutPasteOptions = {|
158+
useLastCursorPosition?: boolean,
159+
pasteInTheForeground?: boolean,
160+
|};
161161

162162
export default class SceneEditor extends React.Component<Props, State> {
163163
static defaultProps = {
@@ -1126,7 +1126,10 @@ export default class SceneEditor extends React.Component<Props, State> {
11261126
return contextMenuItems;
11271127
};
11281128

1129-
copySelection = ({ useLastCursorPosition }: CopyCutPasteOptions = {}) => {
1129+
copySelection = ({
1130+
useLastCursorPosition,
1131+
pasteInTheForeground,
1132+
}: CopyCutPasteOptions = {}) => {
11301133
const serializedSelection = this.instancesSelection
11311134
.getSelectedInstances()
11321135
.map(instance => serializeToJSObject(instance));
@@ -1138,33 +1141,29 @@ export default class SceneEditor extends React.Component<Props, State> {
11381141
Clipboard.set(INSTANCES_CLIPBOARD_KIND, {
11391142
x: position[0],
11401143
y: position[1],
1144+
pasteInTheForeground: !!pasteInTheForeground,
11411145
instances: serializedSelection,
11421146
});
11431147
}
11441148
};
11451149

1146-
cutSelection = (options: CopyCutPasteOptions = {}) => {
1147-
this.copySelection(options);
1150+
cutSelection = ({ useLastCursorPosition }: CopyCutPasteOptions = {}) => {
1151+
this.copySelection({ useLastCursorPosition, pasteInTheForeground: true });
11481152
this.deleteSelection();
11491153
};
11501154

11511155
duplicateSelection = () => {
1156+
const { editor } = this;
1157+
if (!editor) return;
11521158
const serializedSelection = this.instancesSelection
11531159
.getSelectedInstances()
11541160
.map(instance => serializeToJSObject(instance));
11551161

1156-
if (!this.editor) return;
1157-
1158-
const newInstances = serializedSelection.map(serializedInstance => {
1159-
const instance = new gd.InitialInstance();
1160-
unserializeFromJSObject(instance, serializedInstance);
1161-
instance.setX(instance.getX() + 2 * MOVEMENT_BIG_DELTA);
1162-
instance.setY(instance.getY() + 2 * MOVEMENT_BIG_DELTA);
1163-
const newInstance = this.props.initialInstances
1164-
.insertInitialInstance(instance)
1165-
.resetPersistentUuid();
1166-
instance.delete();
1167-
return newInstance;
1162+
const newInstances = editor.addSerializedInstances({
1163+
position: [0, 0],
1164+
copyReferential: [-2 * MOVEMENT_BIG_DELTA, -2 * MOVEMENT_BIG_DELTA],
1165+
serializedInstances: serializedSelection,
1166+
preventSnapToGrid: true,
11681167
});
11691168
this._onInstancesAdded(newInstances);
11701169
this.instancesSelection.clearSelection();
@@ -1176,11 +1175,12 @@ export default class SceneEditor extends React.Component<Props, State> {
11761175
};
11771176

11781177
paste = ({ useLastCursorPosition }: CopyCutPasteOptions = {}) => {
1179-
if (!this.editor) return;
1178+
const { editor } = this;
1179+
if (!editor) return;
11801180

11811181
const position = useLastCursorPosition
1182-
? this.editor.getLastCursorSceneCoordinates()
1183-
: this.editor.getLastContextMenuSceneCoordinates();
1182+
? editor.getLastCursorSceneCoordinates()
1183+
: editor.getLastContextMenuSceneCoordinates();
11841184

11851185
const clipboardContent = Clipboard.get(INSTANCES_CLIPBOARD_KIND);
11861186
const instancesContent = SafeExtractor.extractArrayProperty(
@@ -1189,19 +1189,20 @@ export default class SceneEditor extends React.Component<Props, State> {
11891189
);
11901190
const x = SafeExtractor.extractNumberProperty(clipboardContent, 'x');
11911191
const y = SafeExtractor.extractNumberProperty(clipboardContent, 'y');
1192+
const pasteInTheForeground =
1193+
SafeExtractor.extractBooleanProperty(
1194+
clipboardContent,
1195+
'pasteInTheForeground'
1196+
) || false;
11921197
if (x === null || y === null || instancesContent === null) return;
11931198

1194-
const newInstances = instancesContent.map(serializedInstance => {
1195-
const instance = new gd.InitialInstance();
1196-
unserializeFromJSObject(instance, serializedInstance);
1197-
instance.setX(instance.getX() - x + position[0]);
1198-
instance.setY(instance.getY() - y + position[1]);
1199-
const newInstance = this.props.initialInstances
1200-
.insertInitialInstance(instance)
1201-
.resetPersistentUuid();
1202-
instance.delete();
1203-
return newInstance;
1199+
const newInstances = editor.addSerializedInstances({
1200+
position,
1201+
copyReferential: [x, y],
1202+
serializedInstances: instancesContent,
1203+
addInstancesInTheForeground: pasteInTheForeground,
12041204
});
1205+
12051206
this._onInstancesAdded(newInstances);
12061207
this.instancesSelection.clearSelection();
12071208
this.instancesSelection.selectInstances({

0 commit comments

Comments
 (0)