mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Compare commits
4 Commits
experiment
...
dynamical-
Author | SHA1 | Date | |
---|---|---|---|
![]() |
01f7524e52 | ||
![]() |
1becef95f6 | ||
![]() |
cbe9040b77 | ||
![]() |
e061c5f3c7 |
@@ -27,6 +27,7 @@ type Props = {|
|
||||
onRotateEnd: () => void,
|
||||
toCanvasCoordinates: (x: number, y: number) => [number, number],
|
||||
screenType: ScreenType,
|
||||
showHandles: boolean,
|
||||
|};
|
||||
|
||||
const getButtonSizes = (screenType: ScreenType) => {
|
||||
@@ -77,6 +78,7 @@ export default class SelectedInstances {
|
||||
onRotateEnd: () => void;
|
||||
toCanvasCoordinates: (x: number, y: number) => [number, number];
|
||||
_screenType: ScreenType;
|
||||
showHandles: boolean;
|
||||
|
||||
pixiContainer = new PIXI.Container();
|
||||
rectanglesContainer = new PIXI.Container();
|
||||
@@ -93,6 +95,7 @@ export default class SelectedInstances {
|
||||
onRotateEnd,
|
||||
toCanvasCoordinates,
|
||||
screenType,
|
||||
showHandles,
|
||||
}: Props) {
|
||||
this.instanceMeasurer = instanceMeasurer;
|
||||
this.onResize = onResize;
|
||||
@@ -102,33 +105,36 @@ export default class SelectedInstances {
|
||||
this.toCanvasCoordinates = toCanvasCoordinates;
|
||||
this.instancesSelection = instancesSelection;
|
||||
this._screenType = screenType;
|
||||
this.showHandles = showHandles;
|
||||
|
||||
this.pixiContainer.addChild(this.rectanglesContainer);
|
||||
|
||||
for (const resizeGrabbingLocation of resizeGrabbingLocationValues) {
|
||||
const resizeButton = new PIXI.Graphics();
|
||||
this.resizeButtons[resizeGrabbingLocation] = resizeButton;
|
||||
if (showHandles) {
|
||||
for (const resizeGrabbingLocation of resizeGrabbingLocationValues) {
|
||||
const resizeButton = new PIXI.Graphics();
|
||||
this.resizeButtons[resizeGrabbingLocation] = resizeButton;
|
||||
this._makeButton(
|
||||
resizeButton,
|
||||
event => {
|
||||
this.onResize(event.deltaX, event.deltaY, resizeGrabbingLocation);
|
||||
},
|
||||
() => {
|
||||
this.onResizeEnd();
|
||||
},
|
||||
resizeGrabbingIconNames[resizeGrabbingLocation]
|
||||
);
|
||||
}
|
||||
this._makeButton(
|
||||
resizeButton,
|
||||
this.rotateButton,
|
||||
event => {
|
||||
this.onResize(event.deltaX, event.deltaY, resizeGrabbingLocation);
|
||||
this.onRotate(event.deltaX, event.deltaY);
|
||||
},
|
||||
() => {
|
||||
this.onResizeEnd();
|
||||
this.onRotateEnd();
|
||||
},
|
||||
resizeGrabbingIconNames[resizeGrabbingLocation]
|
||||
'url("res/actions/rotate24_black.png"),auto'
|
||||
);
|
||||
}
|
||||
this._makeButton(
|
||||
this.rotateButton,
|
||||
event => {
|
||||
this.onRotate(event.deltaX, event.deltaY);
|
||||
},
|
||||
() => {
|
||||
this.onRotateEnd();
|
||||
},
|
||||
'url("res/actions/rotate24_black.png"),auto'
|
||||
);
|
||||
}
|
||||
|
||||
setScreenType(screenType: ScreenType) {
|
||||
@@ -260,9 +266,10 @@ export default class SelectedInstances {
|
||||
this.rectanglesContainer.removeChild(this.selectedRectangles.pop());
|
||||
}
|
||||
|
||||
// If there are no unlocked instances, hide the resize buttons.
|
||||
const show =
|
||||
selection.filter(instance => !instance.isLocked()).length !== 0;
|
||||
if (!this.showHandles) return;
|
||||
|
||||
// If there is any unlocked instances, show the resize buttons.
|
||||
const show = selection.some(instance => !instance.isLocked());
|
||||
|
||||
// Position the resize buttons.
|
||||
for (const grabbingLocation of resizeGrabbingLocationValues) {
|
||||
|
@@ -4,30 +4,56 @@ import Rectangle from '../Utils/Rectangle';
|
||||
import { type InstanceMeasurer } from './InstancesRenderer';
|
||||
const gd: libGDevelop = global.gd;
|
||||
|
||||
type Point = { x: number, y: number };
|
||||
|
||||
const normalizeRectangle = (startPoint: Point, endPoint: Point) => {
|
||||
if (startPoint.x > endPoint.x) {
|
||||
const tmp = startPoint.x;
|
||||
startPoint.x = endPoint.x;
|
||||
endPoint.x = tmp;
|
||||
}
|
||||
if (startPoint.y > endPoint.y) {
|
||||
const tmp = startPoint.y;
|
||||
startPoint.y = endPoint.y;
|
||||
endPoint.y = tmp;
|
||||
}
|
||||
};
|
||||
|
||||
const TEMPORARY_SELECTION_DEBOUNCE_DURATION = 200;
|
||||
|
||||
export default class SelectionRectangle {
|
||||
instances: gdInitialInstancesContainer;
|
||||
instanceMeasurer: InstanceMeasurer;
|
||||
toSceneCoordinates: (x: number, y: number) => [number, number];
|
||||
|
||||
pixiRectangle: PIXI.Graphics;
|
||||
selectionRectangleStart: { x: number, y: number } | null;
|
||||
selectionRectangleEnd: { x: number, y: number } | null;
|
||||
selectionRectangleStart: Point | null;
|
||||
selectionRectangleEnd: Point | null;
|
||||
temporarySelectionRectangleStart: Point | null;
|
||||
temporarySelectionRectangleEnd: Point | null;
|
||||
_instancesInSelectionRectangle: gdInitialInstance[];
|
||||
_temporaryInstancesInSelectionRectangle: gdInitialInstance[];
|
||||
_temporarySelectionTimeoutId: TimeoutID | null;
|
||||
onInstancesTemporarySelected: (instances: Array<gdInitialInstance>) => void;
|
||||
|
||||
selector: gdInitialInstanceJSFunctor;
|
||||
temporarySelector: gdInitialInstanceJSFunctor;
|
||||
/**
|
||||
* Used to check if an instance is in the selection rectangle
|
||||
*/
|
||||
_temporaryAABB: Rectangle;
|
||||
_temporarySelectionLastExecutionTime: number;
|
||||
|
||||
constructor({
|
||||
instances,
|
||||
instanceMeasurer,
|
||||
toSceneCoordinates,
|
||||
onInstancesTemporarySelected,
|
||||
}: {
|
||||
instances: gdInitialInstancesContainer,
|
||||
instanceMeasurer: InstanceMeasurer,
|
||||
toSceneCoordinates: (x: number, y: number) => [number, number],
|
||||
onInstancesTemporarySelected: (instances: Array<gdInitialInstance>) => void,
|
||||
}) {
|
||||
this.instances = instances;
|
||||
this.instanceMeasurer = instanceMeasurer;
|
||||
@@ -38,6 +64,10 @@ export default class SelectionRectangle {
|
||||
this.selectionRectangleStart = null;
|
||||
this.selectionRectangleEnd = null;
|
||||
this._instancesInSelectionRectangle = [];
|
||||
this._temporaryInstancesInSelectionRectangle = [];
|
||||
this._temporarySelectionLastExecutionTime = Date.now();
|
||||
this._temporarySelectionTimeoutId = null;
|
||||
this.onInstancesTemporarySelected = onInstancesTemporarySelected;
|
||||
|
||||
this._temporaryAABB = new Rectangle();
|
||||
this.selector = new gd.InitialInstanceJSFunctor();
|
||||
@@ -71,6 +101,41 @@ export default class SelectionRectangle {
|
||||
this._instancesInSelectionRectangle.push(instance);
|
||||
}
|
||||
};
|
||||
this.temporarySelector = new gd.InitialInstanceJSFunctor();
|
||||
// $FlowFixMe - invoke is not writable
|
||||
this.temporarySelector.invoke = instancePtr => {
|
||||
// $FlowFixMe - wrapPointer is not exposed
|
||||
const instance = gd.wrapPointer(instancePtr, gd.InitialInstance);
|
||||
const instanceAABB = this.instanceMeasurer.getInstanceAABB(
|
||||
instance,
|
||||
this._temporaryAABB
|
||||
);
|
||||
|
||||
const {
|
||||
temporarySelectionRectangleEnd,
|
||||
temporarySelectionRectangleStart,
|
||||
} = this;
|
||||
if (!temporarySelectionRectangleStart || !temporarySelectionRectangleEnd)
|
||||
return;
|
||||
|
||||
const selectionSceneStart = toSceneCoordinates(
|
||||
temporarySelectionRectangleStart.x,
|
||||
temporarySelectionRectangleStart.y
|
||||
);
|
||||
const selectionSceneEnd = toSceneCoordinates(
|
||||
temporarySelectionRectangleEnd.x,
|
||||
temporarySelectionRectangleEnd.y
|
||||
);
|
||||
|
||||
if (
|
||||
selectionSceneStart[0] <= instanceAABB.left &&
|
||||
instanceAABB.right <= selectionSceneEnd[0] &&
|
||||
selectionSceneStart[1] <= instanceAABB.top &&
|
||||
instanceAABB.bottom <= selectionSceneEnd[1]
|
||||
) {
|
||||
this._temporaryInstancesInSelectionRectangle.push(instance);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
hasStartedSelectionRectangle() {
|
||||
@@ -82,27 +147,77 @@ export default class SelectionRectangle {
|
||||
this.selectionRectangleEnd = { x, y };
|
||||
};
|
||||
|
||||
updateTemporarySelectedInstances = async (
|
||||
startPoint: Point,
|
||||
endPoint: Point
|
||||
) => {
|
||||
if (
|
||||
Date.now() <=
|
||||
this._temporarySelectionLastExecutionTime +
|
||||
TEMPORARY_SELECTION_DEBOUNCE_DURATION
|
||||
) {
|
||||
// If the method is called before the debounce duration has finished
|
||||
// We cancel the current timeout and we set up a new one that will
|
||||
// execute the selection in the near future.
|
||||
if (this._temporarySelectionTimeoutId) {
|
||||
clearTimeout(this._temporarySelectionTimeoutId);
|
||||
}
|
||||
this._temporarySelectionTimeoutId = setTimeout(
|
||||
() => this.updateTemporarySelectedInstances(startPoint, endPoint),
|
||||
TEMPORARY_SELECTION_DEBOUNCE_DURATION
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!this.temporarySelectionRectangleEnd) {
|
||||
this.temporarySelectionRectangleEnd = {};
|
||||
}
|
||||
if (!this.temporarySelectionRectangleStart) {
|
||||
this.temporarySelectionRectangleStart = {};
|
||||
}
|
||||
// Update temporary selection rectangle to follow the selection rectangle
|
||||
this.temporarySelectionRectangleEnd.x = endPoint.x;
|
||||
this.temporarySelectionRectangleEnd.y = endPoint.y;
|
||||
this.temporarySelectionRectangleStart.x = startPoint.x;
|
||||
this.temporarySelectionRectangleStart.y = startPoint.y;
|
||||
|
||||
this._temporaryInstancesInSelectionRectangle.length = 0;
|
||||
// Mutate the temporary selection rectangle so that it's ready for the
|
||||
// functor iteration.
|
||||
normalizeRectangle(
|
||||
this.temporarySelectionRectangleStart,
|
||||
this.temporarySelectionRectangleEnd
|
||||
);
|
||||
|
||||
this.instances.iterateOverInstances(
|
||||
// $FlowFixMe - gd.castObject is not supporting typings.
|
||||
this.temporarySelector
|
||||
);
|
||||
this._temporarySelectionLastExecutionTime = Date.now();
|
||||
this.onInstancesTemporarySelected(
|
||||
this._temporaryInstancesInSelectionRectangle
|
||||
);
|
||||
};
|
||||
|
||||
updateSelectionRectangle = (lastX: number, lastY: number) => {
|
||||
if (!this.selectionRectangleStart)
|
||||
this.selectionRectangleStart = { x: lastX, y: lastY };
|
||||
|
||||
this.selectionRectangleEnd = { x: lastX, y: lastY };
|
||||
|
||||
this.updateTemporarySelectedInstances(
|
||||
this.selectionRectangleStart,
|
||||
this.selectionRectangleEnd
|
||||
);
|
||||
};
|
||||
|
||||
endSelectionRectangle = () => {
|
||||
endSelectionRectangle = (): Array<gdInitialInstance> => {
|
||||
if (!this.selectionRectangleStart || !this.selectionRectangleEnd) return [];
|
||||
|
||||
this._instancesInSelectionRectangle.length = 0;
|
||||
if (this.selectionRectangleStart.x > this.selectionRectangleEnd.x) {
|
||||
const tmp = this.selectionRectangleStart.x;
|
||||
this.selectionRectangleStart.x = this.selectionRectangleEnd.x;
|
||||
this.selectionRectangleEnd.x = tmp;
|
||||
}
|
||||
if (this.selectionRectangleStart.y > this.selectionRectangleEnd.y) {
|
||||
const tmp = this.selectionRectangleStart.y;
|
||||
this.selectionRectangleStart.y = this.selectionRectangleEnd.y;
|
||||
this.selectionRectangleEnd.y = tmp;
|
||||
}
|
||||
normalizeRectangle(
|
||||
this.selectionRectangleStart,
|
||||
this.selectionRectangleEnd
|
||||
);
|
||||
|
||||
this.instances.iterateOverInstances(
|
||||
// $FlowFixMe - gd.castObject is not supporting typings.
|
||||
|
@@ -103,6 +103,7 @@ export default class InstancesEditor extends Component<Props> {
|
||||
lastContextMenuY = 0;
|
||||
lastCursorX = 0;
|
||||
lastCursorY = 0;
|
||||
instancesTemporarySelection = new InstancesSelection();
|
||||
fpsLimiter = new FpsLimiter(28);
|
||||
canvasArea: ?HTMLDivElement;
|
||||
pixiRenderer: PIXI.Renderer;
|
||||
@@ -112,6 +113,7 @@ export default class InstancesEditor extends Component<Props> {
|
||||
_instancesAdder: InstancesAdder;
|
||||
selectionRectangle: SelectionRectangle;
|
||||
selectedInstances: SelectedInstances;
|
||||
temporarySelectedInstances: SelectedInstances;
|
||||
highlightedInstance: HighlightedInstance;
|
||||
instancesResizer: InstancesResizer;
|
||||
instancesRotator: InstancesRotator;
|
||||
@@ -376,6 +378,7 @@ export default class InstancesEditor extends Component<Props> {
|
||||
instances: props.initialInstances,
|
||||
instanceMeasurer: this.instancesRenderer.getInstanceMeasurer(),
|
||||
toSceneCoordinates: this.viewPosition.toSceneCoordinates,
|
||||
onInstancesTemporarySelected: this.temporarySelectInstances,
|
||||
});
|
||||
this.selectedInstances = new SelectedInstances({
|
||||
instancesSelection: this.props.instancesSelection,
|
||||
@@ -383,6 +386,18 @@ export default class InstancesEditor extends Component<Props> {
|
||||
onResizeEnd: this._onResizeEnd,
|
||||
onRotate: this._onRotate,
|
||||
onRotateEnd: this._onRotateEnd,
|
||||
showHandles: true,
|
||||
instanceMeasurer: this.instancesRenderer.getInstanceMeasurer(),
|
||||
toCanvasCoordinates: this.viewPosition.toCanvasCoordinates,
|
||||
screenType: this.props.screenType,
|
||||
});
|
||||
this.temporarySelectedInstances = new SelectedInstances({
|
||||
instancesSelection: this.instancesTemporarySelection,
|
||||
onResize: () => {},
|
||||
onResizeEnd: () => {},
|
||||
onRotate: () => {},
|
||||
onRotateEnd: () => {},
|
||||
showHandles: false,
|
||||
instanceMeasurer: this.instancesRenderer.getInstanceMeasurer(),
|
||||
toCanvasCoordinates: this.viewPosition.toCanvasCoordinates,
|
||||
screenType: this.props.screenType,
|
||||
@@ -425,6 +440,9 @@ export default class InstancesEditor extends Component<Props> {
|
||||
this.pixiContainer.addChild(this.windowBorder.getPixiObject());
|
||||
this.pixiContainer.addChild(this.windowMask.getPixiObject());
|
||||
this.pixiContainer.addChild(this.selectedInstances.getPixiContainer());
|
||||
this.pixiContainer.addChild(
|
||||
this.temporarySelectedInstances.getPixiContainer()
|
||||
);
|
||||
this.pixiContainer.addChild(this.highlightedInstance.getPixiObject());
|
||||
this.pixiContainer.addChild(this.statusBar.getPixiObject());
|
||||
}
|
||||
@@ -488,6 +506,7 @@ export default class InstancesEditor extends Component<Props> {
|
||||
|
||||
if (nextProps.screenType !== this.props.screenType) {
|
||||
this.selectedInstances.setScreenType(this.props.screenType);
|
||||
this.temporarySelectedInstances.setScreenType(this.props.screenType);
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -612,6 +631,16 @@ export default class InstancesEditor extends Component<Props> {
|
||||
}
|
||||
};
|
||||
|
||||
temporarySelectInstances = (instances: Array<gdInitialInstance>) => {
|
||||
if (this.selectionRectangle.hasStartedSelectionRectangle()) {
|
||||
this.instancesTemporarySelection.selectInstances({
|
||||
instances,
|
||||
multiSelect: false,
|
||||
layersVisibility: this._getLayersVisibility(),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
_getLayersVisibility = () => {
|
||||
const { layout } = this.props;
|
||||
const layersVisibility = {};
|
||||
@@ -636,6 +665,7 @@ export default class InstancesEditor extends Component<Props> {
|
||||
});
|
||||
instancesSelected = this.props.instancesSelection.getSelectedInstances();
|
||||
this.props.onInstancesSelected(instancesSelected);
|
||||
this.instancesTemporarySelection.clearSelection();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -960,6 +990,7 @@ export default class InstancesEditor extends Component<Props> {
|
||||
this.instancesRenderer.render();
|
||||
this.highlightedInstance.render();
|
||||
this.selectedInstances.render();
|
||||
this.temporarySelectedInstances.render();
|
||||
this.selectionRectangle.render();
|
||||
this.windowBorder.render();
|
||||
this.windowMask.render();
|
||||
|
Reference in New Issue
Block a user