Compare commits

...

4 Commits

3 changed files with 186 additions and 33 deletions

View File

@@ -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) {

View File

@@ -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.

View File

@@ -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();