mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Improve Draggable behavior to drag the frontmost object under the cursor/touch (#3066)
* In previous versions, the order was not guaranteed, which could result in a non intuitive result for the player.
This commit is contained in:
@@ -39,6 +39,10 @@ namespace gdjs {
|
||||
this._draggedByDraggableManager = null;
|
||||
}
|
||||
|
||||
_dismissDrag() {
|
||||
this._draggedByDraggableManager = null;
|
||||
}
|
||||
|
||||
_tryBeginDrag(runtimeScene) {
|
||||
if (this._draggedByDraggableManager) {
|
||||
return false;
|
||||
@@ -119,7 +123,15 @@ namespace gdjs {
|
||||
* Handle the dragging
|
||||
*/
|
||||
abstract class DraggableManager {
|
||||
/**
|
||||
* The object has left its original position.
|
||||
* When true, the search for the best object to drag has ended.
|
||||
*/
|
||||
protected _draggingSomething = false;
|
||||
/**
|
||||
* The behavior of the object that is being dragged and that is the best one (i.e: highest Z order) found.
|
||||
*/
|
||||
protected _draggableBehavior: gdjs.DraggableRuntimeBehavior | null = null;
|
||||
protected _xOffset: number = 0;
|
||||
protected _yOffset: number = 0;
|
||||
|
||||
@@ -172,15 +184,26 @@ namespace gdjs {
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
draggableRuntimeBehavior: DraggableRuntimeBehavior
|
||||
) {
|
||||
if (
|
||||
this._draggableBehavior &&
|
||||
draggableRuntimeBehavior.owner.getZOrder() <=
|
||||
this._draggableBehavior.owner.getZOrder()
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
const position = this.getPosition(runtimeScene, draggableRuntimeBehavior);
|
||||
if (
|
||||
!draggableRuntimeBehavior.owner.insideObject(position[0], position[1])
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (this._draggableBehavior) {
|
||||
// The previous best object to drag will not be dragged.
|
||||
this._draggableBehavior._dismissDrag();
|
||||
}
|
||||
this._draggableBehavior = draggableRuntimeBehavior;
|
||||
this._xOffset = position[0] - draggableRuntimeBehavior.owner.getX();
|
||||
this._yOffset = position[1] - draggableRuntimeBehavior.owner.getY();
|
||||
this._draggingSomething = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -189,12 +212,19 @@ namespace gdjs {
|
||||
draggableRuntimeBehavior: DraggableRuntimeBehavior
|
||||
) {
|
||||
const position = this.getPosition(runtimeScene, draggableRuntimeBehavior);
|
||||
draggableRuntimeBehavior.owner.setX(position[0] - this._xOffset);
|
||||
draggableRuntimeBehavior.owner.setY(position[1] - this._yOffset);
|
||||
if (
|
||||
draggableRuntimeBehavior.owner.getX() != position[0] - this._xOffset &&
|
||||
draggableRuntimeBehavior.owner.getY() != position[1] - this._yOffset
|
||||
) {
|
||||
draggableRuntimeBehavior.owner.setX(position[0] - this._xOffset);
|
||||
draggableRuntimeBehavior.owner.setY(position[1] - this._yOffset);
|
||||
this._draggingSomething = true;
|
||||
}
|
||||
}
|
||||
|
||||
endDrag() {
|
||||
this._draggingSomething = false;
|
||||
this._draggableBehavior = null;
|
||||
}
|
||||
|
||||
abstract isDragging(
|
||||
|
@@ -51,112 +51,210 @@ describe('gdjs.DraggableRuntimeBehavior', function () {
|
||||
runtimeScene.addObject(object);
|
||||
runtimeScene.addObject(object2);
|
||||
|
||||
it('should handle mouse', function () {
|
||||
object.setPosition(450, 500);
|
||||
describe('(mouse)', function () {
|
||||
it('can drag an object', function () {
|
||||
object.setPosition(450, 500);
|
||||
|
||||
//Drag'n'drop
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onMouseMove(450, 500);
|
||||
runtimeGame
|
||||
.getInputManager()
|
||||
.onMouseButtonPressed(gdjs.InputManager.MOUSE_LEFT_BUTTON);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onMouseMove(750, 600);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame
|
||||
.getInputManager()
|
||||
.onMouseButtonReleased(gdjs.InputManager.MOUSE_LEFT_BUTTON);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
// Drag'n'drop
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onMouseMove(450, 500);
|
||||
runtimeGame
|
||||
.getInputManager()
|
||||
.onMouseButtonPressed(gdjs.InputManager.MOUSE_LEFT_BUTTON);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onMouseMove(750, 600);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame
|
||||
.getInputManager()
|
||||
.onMouseButtonReleased(gdjs.InputManager.MOUSE_LEFT_BUTTON);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
|
||||
expect(object.getX()).to.be(750);
|
||||
expect(object.getY()).to.be(600);
|
||||
expect(object.getX()).to.be(750);
|
||||
expect(object.getY()).to.be(600);
|
||||
|
||||
//Mouse move with dragging
|
||||
runtimeGame.getInputManager().onMouseMove(600, 600);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
// Mouse move without dragging
|
||||
runtimeGame.getInputManager().onMouseMove(600, 600);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
|
||||
expect(object.getX()).to.be(750);
|
||||
expect(object.getY()).to.be(600);
|
||||
expect(object.getX()).to.be(750);
|
||||
expect(object.getY()).to.be(600);
|
||||
|
||||
//Start dragging again
|
||||
runtimeGame.getInputManager().onMouseMove(750, 600);
|
||||
runtimeGame
|
||||
.getInputManager()
|
||||
.onMouseButtonPressed(gdjs.InputManager.MOUSE_LEFT_BUTTON);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onMouseMove(850, 700);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame
|
||||
.getInputManager()
|
||||
.onMouseButtonReleased(gdjs.InputManager.MOUSE_LEFT_BUTTON);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
// Start dragging again
|
||||
runtimeGame.getInputManager().onMouseMove(750, 600);
|
||||
runtimeGame
|
||||
.getInputManager()
|
||||
.onMouseButtonPressed(gdjs.InputManager.MOUSE_LEFT_BUTTON);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onMouseMove(850, 700);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame
|
||||
.getInputManager()
|
||||
.onMouseButtonReleased(gdjs.InputManager.MOUSE_LEFT_BUTTON);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
|
||||
expect(object.getX()).to.be(850);
|
||||
expect(object.getY()).to.be(700);
|
||||
expect(object.getX()).to.be(850);
|
||||
expect(object.getY()).to.be(700);
|
||||
});
|
||||
|
||||
[false, true].forEach((firstInFront) => {
|
||||
it(`must drag the object in front (${
|
||||
firstInFront ? '1st object' : '2nd object'
|
||||
} in front)`, function () {
|
||||
object.setPosition(450, 500);
|
||||
object2.setPosition(450, 500);
|
||||
if (firstInFront) {
|
||||
object.setZOrder(2);
|
||||
object2.setZOrder(1);
|
||||
} else {
|
||||
object.setZOrder(1);
|
||||
object2.setZOrder(2);
|
||||
}
|
||||
|
||||
// Drag'n'drop
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onMouseMove(450, 500);
|
||||
runtimeGame
|
||||
.getInputManager()
|
||||
.onMouseButtonPressed(gdjs.InputManager.MOUSE_LEFT_BUTTON);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onMouseMove(750, 600);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame
|
||||
.getInputManager()
|
||||
.onMouseButtonReleased(gdjs.InputManager.MOUSE_LEFT_BUTTON);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
|
||||
if (firstInFront) {
|
||||
// The 1st object moved
|
||||
expect(object.getX()).to.be(750);
|
||||
expect(object.getY()).to.be(600);
|
||||
expect(object2.getX()).to.be(450);
|
||||
expect(object2.getY()).to.be(500);
|
||||
} else {
|
||||
// The 2nd object moved
|
||||
expect(object.getX()).to.be(450);
|
||||
expect(object.getY()).to.be(500);
|
||||
expect(object2.getX()).to.be(750);
|
||||
expect(object2.getY()).to.be(600);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
it('should handle touches', function () {
|
||||
runtimeGame.getInputManager().touchSimulateMouse(false);
|
||||
object.setPosition(450, 500);
|
||||
describe('(touch)', function () {
|
||||
it('can drag an object', function () {
|
||||
runtimeGame.getInputManager().touchSimulateMouse(false);
|
||||
object.setPosition(450, 500);
|
||||
|
||||
//Drag'n'drop
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onTouchStart(1, 10, 20);
|
||||
runtimeGame.getInputManager().onTouchStart(0, 450, 500);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onFrameEnded();
|
||||
runtimeGame.getInputManager().onTouchMove(0, 750, 600);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onFrameEnded();
|
||||
runtimeGame.getInputManager().onTouchEnd(0);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onFrameEnded();
|
||||
// Drag'n'drop
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onTouchStart(1, 10, 20);
|
||||
runtimeGame.getInputManager().onTouchStart(0, 450, 500);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onFrameEnded();
|
||||
runtimeGame.getInputManager().onTouchMove(0, 750, 600);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onFrameEnded();
|
||||
runtimeGame.getInputManager().onTouchEnd(0);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onFrameEnded();
|
||||
|
||||
expect(object.getX()).to.be(750);
|
||||
expect(object.getY()).to.be(600);
|
||||
expect(object.getX()).to.be(750);
|
||||
expect(object.getY()).to.be(600);
|
||||
|
||||
//Move another unrelated touch
|
||||
runtimeGame.getInputManager().onTouchMove(1, 750, 600);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onTouchMove(1, 850, 700);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
// Move another unrelated touch
|
||||
runtimeGame.getInputManager().onTouchMove(1, 750, 600);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onTouchMove(1, 850, 700);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
|
||||
expect(object.getX()).to.be(750);
|
||||
expect(object.getY()).to.be(600);
|
||||
expect(object.getX()).to.be(750);
|
||||
expect(object.getY()).to.be(600);
|
||||
|
||||
//Start drag'n'drop with another touch
|
||||
runtimeGame.getInputManager().onTouchEnd(1);
|
||||
runtimeGame.getInputManager().onFrameEnded();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onTouchStart(1, 750, 600);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onTouchMove(1, 850, 700);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onTouchEnd(1);
|
||||
runtimeGame.getInputManager().onFrameEnded();
|
||||
// Start drag'n'drop with another touch
|
||||
runtimeGame.getInputManager().onTouchEnd(1);
|
||||
runtimeGame.getInputManager().onFrameEnded();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onTouchStart(1, 750, 600);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onTouchMove(1, 850, 700);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onTouchEnd(1);
|
||||
runtimeGame.getInputManager().onFrameEnded();
|
||||
|
||||
expect(object.getX()).to.be(850);
|
||||
expect(object.getY()).to.be(700);
|
||||
});
|
||||
it('should handle multitouch', function () {
|
||||
runtimeGame.getInputManager().touchSimulateMouse(false);
|
||||
object.setPosition(450, 500);
|
||||
object2.setPosition(650, 600);
|
||||
expect(object.getX()).to.be(850);
|
||||
expect(object.getY()).to.be(700);
|
||||
});
|
||||
it('can drag 2 objects with multitouch', function () {
|
||||
runtimeGame.getInputManager().touchSimulateMouse(false);
|
||||
object.setPosition(450, 500);
|
||||
object2.setPosition(650, 600);
|
||||
|
||||
//Drag'n'drop
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onTouchStart(2, 450, 500);
|
||||
runtimeGame.getInputManager().onTouchStart(1, 650, 600);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onFrameEnded();
|
||||
runtimeGame.getInputManager().onTouchMove(2, 750, 700);
|
||||
runtimeGame.getInputManager().onTouchMove(1, 100, 200);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onFrameEnded();
|
||||
runtimeGame.getInputManager().onTouchEnd(2);
|
||||
// Drag'n'drop
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onTouchStart(2, 450, 500);
|
||||
runtimeGame.getInputManager().onTouchStart(1, 650, 600);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onFrameEnded();
|
||||
runtimeGame.getInputManager().onTouchMove(2, 750, 700);
|
||||
runtimeGame.getInputManager().onTouchMove(1, 100, 200);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onFrameEnded();
|
||||
runtimeGame.getInputManager().onTouchEnd(1);
|
||||
runtimeGame.getInputManager().onTouchEnd(2);
|
||||
|
||||
expect(object.getX()).to.be(750);
|
||||
expect(object.getY()).to.be(700);
|
||||
expect(object2.getX()).to.be(100);
|
||||
expect(object2.getY()).to.be(200);
|
||||
expect(object.getX()).to.be(750);
|
||||
expect(object.getY()).to.be(700);
|
||||
expect(object2.getX()).to.be(100);
|
||||
expect(object2.getY()).to.be(200);
|
||||
|
||||
// Avoid side effects on the following test cases
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onFrameEnded();
|
||||
});
|
||||
|
||||
[false, true].forEach((firstInFront) => {
|
||||
it(`must drag the object in front (${
|
||||
firstInFront ? '1st object' : '2nd object'
|
||||
} in front)`, function () {
|
||||
object.setPosition(450, 500);
|
||||
object2.setPosition(450, 500);
|
||||
if (firstInFront) {
|
||||
object.setZOrder(2);
|
||||
object2.setZOrder(1);
|
||||
} else {
|
||||
object.setZOrder(1);
|
||||
object2.setZOrder(2);
|
||||
}
|
||||
|
||||
// Drag'n'drop
|
||||
runtimeGame.getInputManager().touchSimulateMouse(false);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onTouchStart(1, 10, 20);
|
||||
runtimeGame.getInputManager().onTouchStart(0, 450, 500);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onFrameEnded();
|
||||
runtimeGame.getInputManager().onTouchMove(0, 750, 600);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onFrameEnded();
|
||||
runtimeGame.getInputManager().onTouchEnd(0);
|
||||
runtimeGame.getInputManager().onTouchEnd(1);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
runtimeGame.getInputManager().onFrameEnded();
|
||||
|
||||
if (firstInFront) {
|
||||
// The 1st object moved
|
||||
expect(object.getX()).to.be(750);
|
||||
expect(object.getY()).to.be(600);
|
||||
expect(object2.getX()).to.be(450);
|
||||
expect(object2.getY()).to.be(500);
|
||||
} else {
|
||||
// The 2nd object moved
|
||||
expect(object.getX()).to.be(450);
|
||||
expect(object.getY()).to.be(500);
|
||||
expect(object2.getX()).to.be(750);
|
||||
expect(object2.getY()).to.be(600);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Reference in New Issue
Block a user