Spaces:
Build error
Build error
const MathUtil = require('../util/math-util'); | |
const { translateScreenPos } = require('../util/pos-math'); | |
const roundToThreeDecimals = number => Math.round(number * 1000) / 1000; | |
class Mouse { | |
constructor (runtime) { | |
this._clientX = 0; | |
this._clientY = 0; | |
this._scratchX = 0; | |
this._scratchY = 0; | |
this._buttons = new Set(); | |
this._isDown = false; | |
this.usesRightClickDown = false; | |
// pm: keep track of clicks | |
this._isClicked = false; | |
this._clickOnStep = -1; | |
/** | |
* Reference to the owning Runtime. | |
* Can be used, for example, to activate hats. | |
* @type{!Runtime} | |
*/ | |
this.runtime = runtime; | |
this.cameraBound = null; | |
// after processing all blocks, we can check if this step is after the one we clicked on | |
this.runtime.on("RUNTIME_STEP_END", () => { | |
if (this.runtime.frameLoop._stepCounter > this._clickOnStep) { | |
this._isClicked = false; | |
} | |
}); | |
} | |
bindToCamera(screen) { | |
this.cameraBound = screen; | |
} | |
removeCameraBinding() { | |
this.cameraBound = null; | |
} | |
/** | |
* Activate "event_whenthisspriteclicked" hats. | |
* @param {Target} target to trigger hats on. | |
* @private | |
*/ | |
_activateClickHats (target) { | |
// Activate both "this sprite clicked" and "stage clicked" | |
// They were separated into two opcodes for labeling, | |
// but should act the same way. | |
// Intentionally not checking isStage to make it work when sharing blocks. | |
this.runtime.startHats('event_whenthisspriteclicked', null, target); | |
this.runtime.startHats('event_whenstageclicked', null, target); | |
if (target.isStage) { | |
this.runtime.startHats('pmEventsExpansion_whenSpriteClicked', { SPRITE: '_stage_' }); | |
return; | |
} | |
if (target.sprite) { | |
this.runtime.startHats('pmEventsExpansion_whenSpriteClicked', { SPRITE: target.sprite.name }); | |
} | |
} | |
/** | |
* Find a target by XY location | |
* @param {number} x X position to be sent to the renderer. | |
* @param {number} y Y position to be sent to the renderer. | |
* @return {Target} the target at that location | |
* @private | |
*/ | |
_pickTarget (x, y) { | |
if (this.runtime.renderer) { | |
const drawableID = this.runtime.renderer.pick(x, y); | |
for (let i = 0; i < this.runtime.targets.length; i++) { | |
const target = this.runtime.targets[i]; | |
if (target.hasOwnProperty('drawableID') && | |
target.drawableID === drawableID) { | |
return target; | |
} | |
} | |
} | |
// Return the stage if no target was found | |
return this.runtime.getTargetForStage(); | |
} | |
/** | |
* Mouse DOM event handler. | |
* @param {object} data Data from DOM event. | |
*/ | |
postData (data) { | |
if (typeof data.x === 'number') { | |
this._clientX = data.x; | |
this._scratchX = MathUtil.clamp( | |
this.runtime.stageWidth * ((data.x / data.canvasWidth) - 0.5), | |
-(this.runtime.stageWidth / 2), | |
(this.runtime.stageWidth / 2) | |
); | |
} | |
if (typeof data.y === 'number') { | |
this._clientY = data.y; | |
this._scratchY = MathUtil.clamp( | |
-this.runtime.stageHeight * ((data.y / data.canvasHeight) - 0.5), | |
-(this.runtime.stageHeight / 2), | |
(this.runtime.stageHeight / 2) | |
); | |
} | |
if (typeof data.isDown !== 'undefined') { | |
// If no button specified, default to left button for compatibility | |
const button = typeof data.button === 'undefined' ? 0 : data.button; | |
if (data.isDown) { | |
this._buttons.add(button); | |
} else { | |
this._buttons.delete(button); | |
} | |
const previousDownState = this._isDown; | |
this._isDown = data.isDown; | |
if (data.isDown) { | |
this._isClicked = true; | |
this._clickOnStep = this.runtime.frameLoop._stepCounter; | |
} | |
// Do not trigger if down state has not changed | |
if (previousDownState === this._isDown) return; | |
// Never trigger click hats at the end of a drag | |
if (data.wasDragged) return; | |
// Do not activate click hats for clicks outside canvas bounds | |
if (!(data.x > 0 && data.x < data.canvasWidth && | |
data.y > 0 && data.y < data.canvasHeight)) return; | |
const target = this._pickTarget(data.x, data.y); | |
const isNewMouseDown = !previousDownState && this._isDown; | |
const isNewMouseUp = previousDownState && !this._isDown; | |
// Draggable targets start click hats on mouse up. | |
// Non-draggable targets start click hats on mouse down. | |
if (target.draggable && isNewMouseUp) { | |
this._activateClickHats(target); | |
} else if (!target.draggable && isNewMouseDown) { | |
this._activateClickHats(target); | |
} | |
} | |
} | |
/** | |
* Get the X position of the mouse in client coordinates. | |
* @return {number} Non-clamped X position of the mouse cursor. | |
*/ | |
getClientX () { | |
return this._clientX; | |
} | |
/** | |
* Get the Y position of the mouse in client coordinates. | |
* @return {number} Non-clamped Y position of the mouse cursor. | |
*/ | |
getClientY () { | |
return this._clientY; | |
} | |
/** | |
* Get the X position of the mouse in scratch coordinates. | |
* @return {number} Clamped and integer rounded X position of the mouse cursor. | |
*/ | |
getScratchX () { | |
const mouseX = this.cameraBound | |
? translateScreenPos(this.runtime, this.cameraBound, this._scratchX, this._scratchY)[0] | |
// ? (this._scratchX * cameraState.scale) - cameraState.pos[0] | |
: this._scratchX; | |
if (this.runtime.runtimeOptions.miscLimits) { | |
return Math.round(mouseX); | |
} | |
return roundToThreeDecimals(mouseX); | |
} | |
/** | |
* Get the Y position of the mouse in scratch coordinates. | |
* @return {number} Clamped and integer rounded Y position of the mouse cursor. | |
*/ | |
getScratchY () { | |
const mouseY = this.cameraBound | |
? translateScreenPos(this.runtime, this.cameraBound, this._scratchX, this._scratchY)[1] | |
// ? (this._scratchY * cameraState.scale) - cameraState.pos[1] | |
: this._scratchY; | |
if (this.runtime.runtimeOptions.miscLimits) { | |
return Math.round(mouseY); | |
} | |
return roundToThreeDecimals(mouseY); | |
} | |
/** | |
* Get the down state of the mouse. | |
* @return {boolean} Is the mouse down? | |
*/ | |
getIsDown () { | |
return this._isDown; | |
} | |
/** | |
* pm: Get if the mouse was pressed down on this tick. | |
* @return {boolean} Is the mouse clicked? | |
*/ | |
getIsClicked () { | |
return this._isClicked; | |
} | |
/** | |
* tw: Get the down state of a specific button of the mouse. | |
* @param {number} button The ID of the button. 0 = left, 1 = middle, 2 = right | |
* @return {boolean} Is the mouse button down? | |
*/ | |
getButtonIsDown (button) { | |
if (button === 2) { | |
this.usesRightClickDown = true; | |
} | |
return this._buttons.has(button); | |
} | |
} | |
module.exports = Mouse; | |