UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

284 lines (222 loc) • 7.62 kB
import Signal from "../../../core/events/signal/Signal.js"; import { SignalBinding } from "../../../core/events/signal/SignalBinding.js"; import { returnTrue } from "../../../core/function/returnTrue.js"; import { DragEvents } from "../../../engine/input/devices/events/DragEvents.js"; import { DraggableElementFlags } from "./Draggable.js"; /** * * @param {DropTarget} dropTarget */ function attachDropValidityIndicators(dropTarget) { const current = []; const tags = []; function update() { tags.forEach(t => dropTarget.view.removeClass(t)); tags.splice(0, tags.length); if (current.length === 0) { //empty } else { let someInvalid = false; current.forEach(({ draggable }) => { if (!dropTarget.validation(draggable)) { someInvalid = true; } }); if (!someInvalid) { //all valid tags.push('drop-target-hover-valid'); } else { //some invalid tags.push('drop-target-hover-invalid'); } } tags.forEach(t => dropTarget.view.addClass(t)); } dropTarget.on.enter.add(handleDragEnter); /** * * @param {Draggable} d */ function removeDraggable(d) { for (let i = 0; i < current.length; i++) { const { draggable, bindings } = current[i]; if (draggable === d) { current.splice(i, 1); //unlink bindings bindings.forEach(b => b.unlink()); update(); return true; } } return false; } /** * * @param {Draggable} draggable */ function handleDragEnter(draggable) { if (draggable.parent === dropTarget) { //skip own child return; } const bindings = [ new SignalBinding(draggable.on.dragFinalized, function () { removeDraggable(draggable); }) ]; bindings.forEach(b => b.link()); current.push({ draggable, bindings }); update(); } dropTarget.on.exit.add(draggable => { removeDraggable(draggable); }); dropTarget.view.on.unlinked.add(() => { const clone = current.slice(); clone.forEach(removeDraggable); }); } export class DropTarget { /** * * @param {View} view * @param {*} domain * @param {function(Draggable):boolean} [validation] * @constructor */ constructor(view, domain, validation = returnTrue) { const self = this; this.view = view; /** * * @type {function(Draggable): boolean} */ this.validation = validation; this.domain = domain; /** * * @type {DragAndDropContext|null} */ this.context = null; const signals = this.on = { added: new Signal(), removed: new Signal(), moved: new Signal(), /** * Draggable enters the target (hover) */ enter: new Signal(), /** * Draggable exists the target (hover) */ exit: new Signal(), /** * Thing that was dropped is invalid */ invalidDrop: new Signal() }; function computeEventDraggable(event) { const uuid = event.dataTransfer.getData('uuid'); if (uuid === "") { //not present, UUID is empty console.warn('uuid is empty'); return null; } const idNumber = parseInt(uuid); return self.context.getElementById(idNumber); } function handleDrop(event) { event.preventDefault(); const draggable = computeEventDraggable(event); if (draggable === null) { console.warn('no element was found with this UUID'); return; } let canDrop = self.validation(draggable); if (!canDrop) { console.warn('drop not allowed'); self.on.invalidDrop.send2(draggable, self); return; } if (draggable.parent === self) { //no change return; } if (draggable.parent !== null) { draggable.parent.on.removed.dispatch(draggable, self.domain); } signals.added.dispatch(draggable, draggable.parent); //set new parent draggable.parent = self; // draggableView.position.copy(view.position); } function handleDragOver(event) { event.preventDefault(); } const isInState_ENTER = []; this.__objectsInState_ENTER = isInState_ENTER; /** * * @param {DragEvent} event */ function handleDragEnter(event) { //figure out what is being dragged over self.context.elements .filter(e => e.getFlag(DraggableElementFlags.BeingDragged)) .forEach(e => { isInState_ENTER.push(e); self.on.enter.send1(e) }); } /** * * @param {DragEvent} event */ function handleDragLeave(event) { //figure out what is being dragged over self.context.elements .filter(e => e.getFlag(DraggableElementFlags.BeingDragged)) .forEach(e => { const i = isInState_ENTER.indexOf(e); isInState_ENTER.splice(i, 1); self.on.exit.send1(e) }); } this.__eventHandlers = { handleDrop, handleDragOver, handleDragEnter, handleDragLeave, }; attachDropValidityIndicators(this); } leaveEnteredElements() { this.__objectsInState_ENTER.forEach(e => { this.on.exit.send1(e); }); //clear this.__objectsInState_ENTER.splice(0, this.__objectsInState_ENTER.length); } handleDragEnded() { this.leaveEnteredElements(); } link() { const eventHandlers = this.__eventHandlers; const el = this.view.el; el.addEventListener(DragEvents.Drop, eventHandlers.handleDrop); el.addEventListener(DragEvents.DragOver, eventHandlers.handleDragOver); el.addEventListener(DragEvents.DragEnter, eventHandlers.handleDragEnter); el.addEventListener(DragEvents.DragLeave, eventHandlers.handleDragLeave); } unlink() { const eventHandlers = this.__eventHandlers; const el = this.view.el; el.removeEventListener(DragEvents.Drop, eventHandlers.handleDrop); el.removeEventListener(DragEvents.DragOver, eventHandlers.handleDragOver); el.removeEventListener(DragEvents.DragEnter, eventHandlers.handleDragEnter); el.removeEventListener(DragEvents.DragLeave, eventHandlers.handleDragLeave); } }