Compare commits

...

4 Commits

3 changed files with 186 additions and 33 deletions

View File

@@ -27,6 +27,7 @@ type Props = {|
onRotateEnd: () => void, onRotateEnd: () => void,
toCanvasCoordinates: (x: number, y: number) => [number, number], toCanvasCoordinates: (x: number, y: number) => [number, number],
screenType: ScreenType, screenType: ScreenType,
showHandles: boolean,
|}; |};
const getButtonSizes = (screenType: ScreenType) => { const getButtonSizes = (screenType: ScreenType) => {
@@ -77,6 +78,7 @@ export default class SelectedInstances {
onRotateEnd: () => void; onRotateEnd: () => void;
toCanvasCoordinates: (x: number, y: number) => [number, number]; toCanvasCoordinates: (x: number, y: number) => [number, number];
_screenType: ScreenType; _screenType: ScreenType;
showHandles: boolean;
pixiContainer = new PIXI.Container(); pixiContainer = new PIXI.Container();
rectanglesContainer = new PIXI.Container(); rectanglesContainer = new PIXI.Container();
@@ -93,6 +95,7 @@ export default class SelectedInstances {
onRotateEnd, onRotateEnd,
toCanvasCoordinates, toCanvasCoordinates,
screenType, screenType,
showHandles,
}: Props) { }: Props) {
this.instanceMeasurer = instanceMeasurer; this.instanceMeasurer = instanceMeasurer;
this.onResize = onResize; this.onResize = onResize;
@@ -102,33 +105,36 @@ export default class SelectedInstances {
this.toCanvasCoordinates = toCanvasCoordinates; this.toCanvasCoordinates = toCanvasCoordinates;
this.instancesSelection = instancesSelection; this.instancesSelection = instancesSelection;
this._screenType = screenType; this._screenType = screenType;
this.showHandles = showHandles;
this.pixiContainer.addChild(this.rectanglesContainer); this.pixiContainer.addChild(this.rectanglesContainer);
for (const resizeGrabbingLocation of resizeGrabbingLocationValues) { if (showHandles) {
const resizeButton = new PIXI.Graphics(); for (const resizeGrabbingLocation of resizeGrabbingLocationValues) {
this.resizeButtons[resizeGrabbingLocation] = resizeButton; 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( this._makeButton(
resizeButton, this.rotateButton,
event => { 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) { setScreenType(screenType: ScreenType) {
@@ -260,9 +266,10 @@ export default class SelectedInstances {
this.rectanglesContainer.removeChild(this.selectedRectangles.pop()); this.rectanglesContainer.removeChild(this.selectedRectangles.pop());
} }
// If there are no unlocked instances, hide the resize buttons. if (!this.showHandles) return;
const show =
selection.filter(instance => !instance.isLocked()).length !== 0; // If there is any unlocked instances, show the resize buttons.
const show = selection.some(instance => !instance.isLocked());
// Position the resize buttons. // Position the resize buttons.
for (const grabbingLocation of resizeGrabbingLocationValues) { for (const grabbingLocation of resizeGrabbingLocationValues) {

View File

@@ -4,30 +4,56 @@ import Rectangle from '../Utils/Rectangle';
import { type InstanceMeasurer } from './InstancesRenderer'; import { type InstanceMeasurer } from './InstancesRenderer';
const gd: libGDevelop = global.gd; 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 { export default class SelectionRectangle {
instances: gdInitialInstancesContainer; instances: gdInitialInstancesContainer;
instanceMeasurer: InstanceMeasurer; instanceMeasurer: InstanceMeasurer;
toSceneCoordinates: (x: number, y: number) => [number, number]; toSceneCoordinates: (x: number, y: number) => [number, number];
pixiRectangle: PIXI.Graphics; pixiRectangle: PIXI.Graphics;
selectionRectangleStart: { x: number, y: number } | null; selectionRectangleStart: Point | null;
selectionRectangleEnd: { x: number, y: number } | null; selectionRectangleEnd: Point | null;
temporarySelectionRectangleStart: Point | null;
temporarySelectionRectangleEnd: Point | null;
_instancesInSelectionRectangle: gdInitialInstance[]; _instancesInSelectionRectangle: gdInitialInstance[];
_temporaryInstancesInSelectionRectangle: gdInitialInstance[];
_temporarySelectionTimeoutId: TimeoutID | null;
onInstancesTemporarySelected: (instances: Array<gdInitialInstance>) => void;
selector: gdInitialInstanceJSFunctor; selector: gdInitialInstanceJSFunctor;
temporarySelector: gdInitialInstanceJSFunctor;
/** /**
* Used to check if an instance is in the selection rectangle * Used to check if an instance is in the selection rectangle
*/ */
_temporaryAABB: Rectangle; _temporaryAABB: Rectangle;
_temporarySelectionLastExecutionTime: number;
constructor({ constructor({
instances, instances,
instanceMeasurer, instanceMeasurer,
toSceneCoordinates, toSceneCoordinates,
onInstancesTemporarySelected,
}: { }: {
instances: gdInitialInstancesContainer, instances: gdInitialInstancesContainer,
instanceMeasurer: InstanceMeasurer, instanceMeasurer: InstanceMeasurer,
toSceneCoordinates: (x: number, y: number) => [number, number], toSceneCoordinates: (x: number, y: number) => [number, number],
onInstancesTemporarySelected: (instances: Array<gdInitialInstance>) => void,
}) { }) {
this.instances = instances; this.instances = instances;
this.instanceMeasurer = instanceMeasurer; this.instanceMeasurer = instanceMeasurer;
@@ -38,6 +64,10 @@ export default class SelectionRectangle {
this.selectionRectangleStart = null; this.selectionRectangleStart = null;
this.selectionRectangleEnd = null; this.selectionRectangleEnd = null;
this._instancesInSelectionRectangle = []; this._instancesInSelectionRectangle = [];
this._temporaryInstancesInSelectionRectangle = [];
this._temporarySelectionLastExecutionTime = Date.now();
this._temporarySelectionTimeoutId = null;
this.onInstancesTemporarySelected = onInstancesTemporarySelected;
this._temporaryAABB = new Rectangle(); this._temporaryAABB = new Rectangle();
this.selector = new gd.InitialInstanceJSFunctor(); this.selector = new gd.InitialInstanceJSFunctor();
@@ -71,6 +101,41 @@ export default class SelectionRectangle {
this._instancesInSelectionRectangle.push(instance); 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() { hasStartedSelectionRectangle() {
@@ -82,27 +147,77 @@ export default class SelectionRectangle {
this.selectionRectangleEnd = { x, y }; 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) => { updateSelectionRectangle = (lastX: number, lastY: number) => {
if (!this.selectionRectangleStart) if (!this.selectionRectangleStart)
this.selectionRectangleStart = { x: lastX, y: lastY }; this.selectionRectangleStart = { x: lastX, y: lastY };
this.selectionRectangleEnd = { 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 []; if (!this.selectionRectangleStart || !this.selectionRectangleEnd) return [];
this._instancesInSelectionRectangle.length = 0; this._instancesInSelectionRectangle.length = 0;
if (this.selectionRectangleStart.x > this.selectionRectangleEnd.x) { normalizeRectangle(
const tmp = this.selectionRectangleStart.x; this.selectionRectangleStart,
this.selectionRectangleStart.x = this.selectionRectangleEnd.x; this.selectionRectangleEnd
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;
}
this.instances.iterateOverInstances( this.instances.iterateOverInstances(
// $FlowFixMe - gd.castObject is not supporting typings. // $FlowFixMe - gd.castObject is not supporting typings.

View File

@@ -103,6 +103,7 @@ export default class InstancesEditor extends Component<Props> {
lastContextMenuY = 0; lastContextMenuY = 0;
lastCursorX = 0; lastCursorX = 0;
lastCursorY = 0; lastCursorY = 0;
instancesTemporarySelection = new InstancesSelection();
fpsLimiter = new FpsLimiter(28); fpsLimiter = new FpsLimiter(28);
canvasArea: ?HTMLDivElement; canvasArea: ?HTMLDivElement;
pixiRenderer: PIXI.Renderer; pixiRenderer: PIXI.Renderer;
@@ -112,6 +113,7 @@ export default class InstancesEditor extends Component<Props> {
_instancesAdder: InstancesAdder; _instancesAdder: InstancesAdder;
selectionRectangle: SelectionRectangle; selectionRectangle: SelectionRectangle;
selectedInstances: SelectedInstances; selectedInstances: SelectedInstances;
temporarySelectedInstances: SelectedInstances;
highlightedInstance: HighlightedInstance; highlightedInstance: HighlightedInstance;
instancesResizer: InstancesResizer; instancesResizer: InstancesResizer;
instancesRotator: InstancesRotator; instancesRotator: InstancesRotator;
@@ -376,6 +378,7 @@ export default class InstancesEditor extends Component<Props> {
instances: props.initialInstances, instances: props.initialInstances,
instanceMeasurer: this.instancesRenderer.getInstanceMeasurer(), instanceMeasurer: this.instancesRenderer.getInstanceMeasurer(),
toSceneCoordinates: this.viewPosition.toSceneCoordinates, toSceneCoordinates: this.viewPosition.toSceneCoordinates,
onInstancesTemporarySelected: this.temporarySelectInstances,
}); });
this.selectedInstances = new SelectedInstances({ this.selectedInstances = new SelectedInstances({
instancesSelection: this.props.instancesSelection, instancesSelection: this.props.instancesSelection,
@@ -383,6 +386,18 @@ export default class InstancesEditor extends Component<Props> {
onResizeEnd: this._onResizeEnd, onResizeEnd: this._onResizeEnd,
onRotate: this._onRotate, onRotate: this._onRotate,
onRotateEnd: this._onRotateEnd, 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(), instanceMeasurer: this.instancesRenderer.getInstanceMeasurer(),
toCanvasCoordinates: this.viewPosition.toCanvasCoordinates, toCanvasCoordinates: this.viewPosition.toCanvasCoordinates,
screenType: this.props.screenType, 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.windowBorder.getPixiObject());
this.pixiContainer.addChild(this.windowMask.getPixiObject()); this.pixiContainer.addChild(this.windowMask.getPixiObject());
this.pixiContainer.addChild(this.selectedInstances.getPixiContainer()); this.pixiContainer.addChild(this.selectedInstances.getPixiContainer());
this.pixiContainer.addChild(
this.temporarySelectedInstances.getPixiContainer()
);
this.pixiContainer.addChild(this.highlightedInstance.getPixiObject()); this.pixiContainer.addChild(this.highlightedInstance.getPixiObject());
this.pixiContainer.addChild(this.statusBar.getPixiObject()); this.pixiContainer.addChild(this.statusBar.getPixiObject());
} }
@@ -488,6 +506,7 @@ export default class InstancesEditor extends Component<Props> {
if (nextProps.screenType !== this.props.screenType) { if (nextProps.screenType !== this.props.screenType) {
this.selectedInstances.setScreenType(this.props.screenType); this.selectedInstances.setScreenType(this.props.screenType);
this.temporarySelectedInstances.setScreenType(this.props.screenType);
} }
if ( 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 = () => { _getLayersVisibility = () => {
const { layout } = this.props; const { layout } = this.props;
const layersVisibility = {}; const layersVisibility = {};
@@ -636,6 +665,7 @@ export default class InstancesEditor extends Component<Props> {
}); });
instancesSelected = this.props.instancesSelection.getSelectedInstances(); instancesSelected = this.props.instancesSelection.getSelectedInstances();
this.props.onInstancesSelected(instancesSelected); this.props.onInstancesSelected(instancesSelected);
this.instancesTemporarySelection.clearSelection();
} }
}; };
@@ -960,6 +990,7 @@ export default class InstancesEditor extends Component<Props> {
this.instancesRenderer.render(); this.instancesRenderer.render();
this.highlightedInstance.render(); this.highlightedInstance.render();
this.selectedInstances.render(); this.selectedInstances.render();
this.temporarySelectedInstances.render();
this.selectionRectangle.render(); this.selectionRectangle.render();
this.windowBorder.render(); this.windowBorder.render();
this.windowMask.render(); this.windowMask.render();