diff --git a/gulp/gulpfile.js b/gulp/gulpfile.js index 7ac426a1..7b0416ca 100644 --- a/gulp/gulpfile.js +++ b/gulp/gulpfile.js @@ -71,10 +71,6 @@ releaseUploader.gulptasksReleaseUploader($, gulp, buildFolder); const translations = require("./translations"); translations.gulptasksTranslations($, gulp, buildFolder); -// FIXME -// const cordova = require("./cordova"); -// cordova.gulptasksCordova($, gulp, buildFolder); - ///////////////////// BUILD TASKS ///////////////////// // Cleans up everything diff --git a/src/js/core/click_detector.js b/src/js/core/click_detector.js index ea6abf48..fb62f0f1 100644 --- a/src/js/core/click_detector.js +++ b/src/js/core/click_detector.js @@ -1,467 +1,465 @@ -import { createLogger } from "../core/logging"; -import { Signal } from "../core/signal"; -import { fastArrayDelete, fastArrayDeleteValueIfContained } from "./utils"; -import { Vector } from "./vector"; -import { IS_MOBILE, SUPPORT_TOUCH } from "./config"; -import { SOUNDS } from "../platform/sound"; -import { GLOBAL_APP } from "./globals"; - -const logger = createLogger("click_detector"); - -export const MAX_MOVE_DISTANCE_PX = IS_MOBILE ? 20 : 80; - -// For debugging -const registerClickDetectors = G_IS_DEV && true; -if (registerClickDetectors) { - /** @type {Array} */ - window.activeClickDetectors = []; -} - -// Store active click detectors so we can cancel them -/** @type {Array} */ -const ongoingClickDetectors = []; - -// Store when the last touch event was registered, to avoid accepting a touch *and* a click event - -export let clickDetectorGlobals = { - lastTouchTime: -1000, -}; - -/** - * Click detector creation payload typehints - * @typedef {{ - * consumeEvents?: boolean, - * preventDefault?: boolean, - * applyCssClass?: string, - * captureTouchmove?: boolean, - * targetOnly?: boolean, - * maxDistance?: number, - * clickSound?: string, - * preventClick?: boolean, - * }} ClickDetectorConstructorArgs - */ - -// Detects clicks -export class ClickDetector { - /** - * - * @param {Element} element - * @param {object} param1 - * @param {boolean=} param1.consumeEvents Whether to call stopPropagation - * (Useful for nested elements where the parent has a click handler as wel) - * @param {boolean=} param1.preventDefault Whether to call preventDefault (Usually makes the handler faster) - * @param {string=} param1.applyCssClass The css class to add while the element is pressed - * @param {boolean=} param1.captureTouchmove Whether to capture touchmove events as well - * @param {boolean=} param1.targetOnly Whether to also accept clicks on child elements (e.target !== element) - * @param {number=} param1.maxDistance The maximum distance in pixels to accept clicks - * @param {string=} param1.clickSound Sound key to play on touchdown - * @param {boolean=} param1.preventClick Whether to prevent click events - */ - constructor( - element, - { - consumeEvents = false, - preventDefault = true, - applyCssClass = "pressed", - captureTouchmove = false, - targetOnly = false, - maxDistance = MAX_MOVE_DISTANCE_PX, - clickSound = SOUNDS.uiClick, - preventClick = false, - } - ) { - assert(element, "No element given!"); - this.clickDownPosition = null; - - this.consumeEvents = consumeEvents; - this.preventDefault = preventDefault; - this.applyCssClass = applyCssClass; - this.captureTouchmove = captureTouchmove; - this.targetOnly = targetOnly; - this.clickSound = clickSound; - this.maxDistance = maxDistance; - this.preventClick = preventClick; - - // Signals - this.click = new Signal(); - this.rightClick = new Signal(); - this.touchstart = new Signal(); - this.touchmove = new Signal(); - this.touchend = new Signal(); - this.touchcancel = new Signal(); - - // Simple signals which just receive the touch position - this.touchstartSimple = new Signal(); - this.touchmoveSimple = new Signal(); - this.touchendSimple = new Signal(); - - // Store time of touch start - this.clickStartTime = null; - - // A click can be cancelled if another detector registers a click - this.cancelled = false; - - this.internalBindTo(/** @type {HTMLElement} */ (element)); - } - - /** - * Cleans up all event listeners of this detector - */ - cleanup() { - if (this.element) { - if (registerClickDetectors) { - const index = window.activeClickDetectors.indexOf(this); - if (index < 0) { - logger.error("Click detector cleanup but is not active"); - } else { - window.activeClickDetectors.splice(index, 1); - } - } - const options = this.internalGetEventListenerOptions(); - - if (SUPPORT_TOUCH) { - this.element.removeEventListener("touchstart", this.handlerTouchStart, options); - this.element.removeEventListener("touchend", this.handlerTouchEnd, options); - this.element.removeEventListener("touchcancel", this.handlerTouchCancel, options); - } - - this.element.removeEventListener("mouseup", this.handlerTouchStart, options); - this.element.removeEventListener("mousedown", this.handlerTouchEnd, options); - this.element.removeEventListener("mouseout", this.handlerTouchCancel, options); - - if (this.captureTouchmove) { - if (SUPPORT_TOUCH) { - this.element.removeEventListener("touchmove", this.handlerTouchMove, options); - } - this.element.removeEventListener("mousemove", this.handlerTouchMove, options); - } - - if (this.preventClick) { - this.element.removeEventListener("click", this.handlerPreventClick, options); - } - - this.click.removeAll(); - this.touchstart.removeAll(); - this.touchmove.removeAll(); - this.touchend.removeAll(); - this.touchcancel.removeAll(); - - // TODO: Remove pointer captures - - this.element = null; - } - } - - // INTERNAL METHODS - - /** - * - * @param {Event} event - */ - internalPreventClick(event) { - window.focus(); - event.preventDefault(); - } - - /** - * Internal method to get the options to pass to an event listener - */ - internalGetEventListenerOptions() { - return { - capture: this.consumeEvents, - passive: !this.preventDefault, - }; - } - - /** - * Binds the click detector to an element - * @param {HTMLElement} element - */ - internalBindTo(element) { - const options = this.internalGetEventListenerOptions(); - - this.handlerTouchStart = this.internalOnPointerDown.bind(this); - this.handlerTouchEnd = this.internalOnPointerEnd.bind(this); - this.handlerTouchMove = this.internalOnPointerMove.bind(this); - this.handlerTouchCancel = this.internalOnTouchCancel.bind(this); - - if (this.preventClick) { - this.handlerPreventClick = this.internalPreventClick.bind(this); - element.addEventListener("click", this.handlerPreventClick, options); - } - - if (SUPPORT_TOUCH) { - element.addEventListener("touchstart", this.handlerTouchStart, options); - element.addEventListener("touchend", this.handlerTouchEnd, options); - element.addEventListener("touchcancel", this.handlerTouchCancel, options); - } - - element.addEventListener("mousedown", this.handlerTouchStart, options); - element.addEventListener("mouseup", this.handlerTouchEnd, options); - element.addEventListener("mouseout", this.handlerTouchCancel, options); - - if (this.captureTouchmove) { - if (SUPPORT_TOUCH) { - element.addEventListener("touchmove", this.handlerTouchMove, options); - } - element.addEventListener("mousemove", this.handlerTouchMove, options); - } - - if (registerClickDetectors) { - window.activeClickDetectors.push(this); - } - this.element = element; - } - - /** - * Returns if the bound element is currently in the DOM. - */ - internalIsDomElementAttached() { - return this.element && document.documentElement.contains(this.element); - } - - /** - * Checks if the given event is relevant for this detector - * @param {TouchEvent|MouseEvent} event - */ - internalEventPreHandler(event, expectedRemainingTouches = 1) { - if (!this.element) { - // Already cleaned up - return false; - } - - if (this.targetOnly && event.target !== this.element) { - // Clicked a child element - return false; - } - - // Stop any propagation and defaults if configured - if (this.consumeEvents && event.cancelable) { - event.stopPropagation(); - } - - if (this.preventDefault && event.cancelable) { - event.preventDefault(); - } - - if (window.TouchEvent && event instanceof TouchEvent) { - clickDetectorGlobals.lastTouchTime = performance.now(); - - // console.log("Got touches", event.targetTouches.length, "vs", expectedRemainingTouches); - if (event.targetTouches.length !== expectedRemainingTouches) { - return false; - } - } - - if (event instanceof MouseEvent) { - if (performance.now() - clickDetectorGlobals.lastTouchTime < 1000.0) { - return false; - } - } - - return true; - } - - /** - * Extracts the mous position from an event - * @param {TouchEvent|MouseEvent} event - * @returns {Vector} The client space position - */ - static extractPointerPosition(event) { - if (window.TouchEvent && event instanceof TouchEvent) { - if (event.changedTouches.length !== 1) { - logger.warn( - "Got unexpected target touches:", - event.targetTouches.length, - "->", - event.targetTouches - ); - return new Vector(0, 0); - } - - const touch = event.changedTouches[0]; - return new Vector(touch.clientX, touch.clientY); - } - - if (event instanceof MouseEvent) { - return new Vector(event.clientX, event.clientY); - } - - assertAlways(false, "Got unknown event: " + event); - - return new Vector(0, 0); - } - - /** - * Cacnels all ongoing events on this detector - */ - cancelOngoingEvents() { - if (this.applyCssClass && this.element) { - this.element.classList.remove(this.applyCssClass); - } - this.clickDownPosition = null; - this.clickStartTime = null; - this.cancelled = true; - fastArrayDeleteValueIfContained(ongoingClickDetectors, this); - } - - /** - * Internal pointer down handler - * @param {TouchEvent|MouseEvent} event - */ - internalOnPointerDown(event) { - window.focus(); - - if (!this.internalEventPreHandler(event, 1)) { - return false; - } - - const position = /** @type {typeof ClickDetector} */ (this.constructor).extractPointerPosition(event); - - if (event instanceof MouseEvent) { - const isRightClick = event.button === 2; - if (isRightClick) { - // Ignore right clicks - this.rightClick.dispatch(position, event); - this.cancelled = true; - this.clickDownPosition = null; - return; - } - } - - if (this.clickDownPosition) { - logger.warn("Ignoring double click"); - return false; - } - - this.cancelled = false; - this.touchstart.dispatch(event); - - // Store where the touch started - this.clickDownPosition = position; - this.clickStartTime = performance.now(); - this.touchstartSimple.dispatch(this.clickDownPosition.x, this.clickDownPosition.y); - - // If we are not currently within a click, register it - if (ongoingClickDetectors.indexOf(this) < 0) { - ongoingClickDetectors.push(this); - } else { - logger.warn("Click detector got pointer down of active pointer twice"); - } - - // If we should apply any classes, do this now - if (this.applyCssClass) { - this.element.classList.add(this.applyCssClass); - } - - // If we should play any sound, do this - if (this.clickSound) { - GLOBAL_APP.sound.playUiSound(this.clickSound); - } - - return false; - } - - /** - * Internal pointer move handler - * @param {TouchEvent|MouseEvent} event - */ - internalOnPointerMove(event) { - if (!this.internalEventPreHandler(event, 1)) { - return false; - } - this.touchmove.dispatch(event); - const pos = /** @type {typeof ClickDetector} */ (this.constructor).extractPointerPosition(event); - this.touchmoveSimple.dispatch(pos.x, pos.y); - return false; - } - - /** - * Internal pointer end handler - * @param {TouchEvent|MouseEvent} event - */ - internalOnPointerEnd(event) { - window.focus(); - - if (!this.internalEventPreHandler(event, 0)) { - return false; - } - - if (this.cancelled) { - // warn(this, "Not dispatching touchend on cancelled listener"); - return false; - } - - if (event instanceof MouseEvent) { - const isRightClick = event.button === 2; - if (isRightClick) { - return; - } - } - - const index = ongoingClickDetectors.indexOf(this); - if (index < 0) { - logger.warn("Got pointer end but click detector is not in pressed state"); - } else { - fastArrayDelete(ongoingClickDetectors, index); - } - - let dispatchClick = false; - let dispatchClickPos = null; - - // Check for correct down position, otherwise must have pinched or so - if (this.clickDownPosition) { - const pos = /** @type {typeof ClickDetector} */ (this.constructor).extractPointerPosition(event); - const distance = pos.distance(this.clickDownPosition); - if (!IS_MOBILE || distance <= this.maxDistance) { - dispatchClick = true; - dispatchClickPos = pos; - } else { - console.warn("[ClickDetector] Touch does not count as click:", "(was", distance, ")"); - } - } - - this.clickDownPosition = null; - this.clickStartTime = null; - - if (this.applyCssClass) { - this.element.classList.remove(this.applyCssClass); - } - - // Dispatch in the end to avoid the element getting invalidated - // Also make sure that the element is still in the dom - if (this.internalIsDomElementAttached()) { - this.touchend.dispatch(event); - this.touchendSimple.dispatch(); - - if (dispatchClick) { - const detectors = ongoingClickDetectors.slice(); - for (let i = 0; i < detectors.length; ++i) { - detectors[i].cancelOngoingEvents(); - } - this.click.dispatch(dispatchClickPos, event); - } - } - return false; - } - - /** - * Internal touch cancel handler - * @param {TouchEvent|MouseEvent} event - */ - internalOnTouchCancel(event) { - if (!this.internalEventPreHandler(event, 0)) { - return false; - } - - if (this.cancelled) { - // warn(this, "Not dispatching touchcancel on cancelled listener"); - return false; - } - - this.cancelOngoingEvents(); - this.touchcancel.dispatch(event); - this.touchendSimple.dispatch(event); - return false; - } -} +import { createLogger } from "../core/logging"; +import { Signal } from "../core/signal"; +import { fastArrayDelete, fastArrayDeleteValueIfContained } from "./utils"; +import { Vector } from "./vector"; +import { IS_MOBILE, SUPPORT_TOUCH } from "./config"; +import { SOUNDS } from "../platform/sound"; +import { GLOBAL_APP } from "./globals"; + +const logger = createLogger("click_detector"); + +export const MAX_MOVE_DISTANCE_PX = IS_MOBILE ? 20 : 80; + +// For debugging +const registerClickDetectors = G_IS_DEV && true; +if (registerClickDetectors) { + /** @type {Array} */ + window.activeClickDetectors = []; +} + +// Store active click detectors so we can cancel them +/** @type {Array} */ +const ongoingClickDetectors = []; + +// Store when the last touch event was registered, to avoid accepting a touch *and* a click event + +export let clickDetectorGlobals = { + lastTouchTime: -1000, +}; + +/** + * Click detector creation payload typehints + * @typedef {{ + * consumeEvents?: boolean, + * preventDefault?: boolean, + * applyCssClass?: string, + * captureTouchmove?: boolean, + * targetOnly?: boolean, + * maxDistance?: number, + * clickSound?: string, + * preventClick?: boolean, + * }} ClickDetectorConstructorArgs + */ + +// Detects clicks +export class ClickDetector { + /** + * + * @param {Element} element + * @param {object} param1 + * @param {boolean=} param1.consumeEvents Whether to call stopPropagation + * (Useful for nested elements where the parent has a click handler as wel) + * @param {boolean=} param1.preventDefault Whether to call preventDefault (Usually makes the handler faster) + * @param {string=} param1.applyCssClass The css class to add while the element is pressed + * @param {boolean=} param1.captureTouchmove Whether to capture touchmove events as well + * @param {boolean=} param1.targetOnly Whether to also accept clicks on child elements (e.target !== element) + * @param {number=} param1.maxDistance The maximum distance in pixels to accept clicks + * @param {string=} param1.clickSound Sound key to play on touchdown + * @param {boolean=} param1.preventClick Whether to prevent click events + */ + constructor( + element, + { + consumeEvents = false, + preventDefault = true, + applyCssClass = "pressed", + captureTouchmove = false, + targetOnly = false, + maxDistance = MAX_MOVE_DISTANCE_PX, + clickSound = SOUNDS.uiClick, + preventClick = false, + } + ) { + assert(element, "No element given!"); + this.clickDownPosition = null; + + this.consumeEvents = consumeEvents; + this.preventDefault = preventDefault; + this.applyCssClass = applyCssClass; + this.captureTouchmove = captureTouchmove; + this.targetOnly = targetOnly; + this.clickSound = clickSound; + this.maxDistance = maxDistance; + this.preventClick = preventClick; + + // Signals + this.click = new Signal(); + this.rightClick = new Signal(); + this.touchstart = new Signal(); + this.touchmove = new Signal(); + this.touchend = new Signal(); + this.touchcancel = new Signal(); + + // Simple signals which just receive the touch position + this.touchstartSimple = new Signal(); + this.touchmoveSimple = new Signal(); + this.touchendSimple = new Signal(); + + // Store time of touch start + this.clickStartTime = null; + + // A click can be cancelled if another detector registers a click + this.cancelled = false; + + this.internalBindTo(/** @type {HTMLElement} */ (element)); + } + + /** + * Cleans up all event listeners of this detector + */ + cleanup() { + if (this.element) { + if (registerClickDetectors) { + const index = window.activeClickDetectors.indexOf(this); + if (index < 0) { + logger.error("Click detector cleanup but is not active"); + } else { + window.activeClickDetectors.splice(index, 1); + } + } + const options = this.internalGetEventListenerOptions(); + + if (SUPPORT_TOUCH) { + this.element.removeEventListener("touchstart", this.handlerTouchStart, options); + this.element.removeEventListener("touchend", this.handlerTouchEnd, options); + this.element.removeEventListener("touchcancel", this.handlerTouchCancel, options); + } + + this.element.removeEventListener("mouseup", this.handlerTouchStart, options); + this.element.removeEventListener("mousedown", this.handlerTouchEnd, options); + this.element.removeEventListener("mouseout", this.handlerTouchCancel, options); + + if (this.captureTouchmove) { + if (SUPPORT_TOUCH) { + this.element.removeEventListener("touchmove", this.handlerTouchMove, options); + } + this.element.removeEventListener("mousemove", this.handlerTouchMove, options); + } + + if (this.preventClick) { + this.element.removeEventListener("click", this.handlerPreventClick, options); + } + + this.click.removeAll(); + this.touchstart.removeAll(); + this.touchmove.removeAll(); + this.touchend.removeAll(); + this.touchcancel.removeAll(); + + this.element = null; + } + } + + // INTERNAL METHODS + + /** + * + * @param {Event} event + */ + internalPreventClick(event) { + window.focus(); + event.preventDefault(); + } + + /** + * Internal method to get the options to pass to an event listener + */ + internalGetEventListenerOptions() { + return { + capture: this.consumeEvents, + passive: !this.preventDefault, + }; + } + + /** + * Binds the click detector to an element + * @param {HTMLElement} element + */ + internalBindTo(element) { + const options = this.internalGetEventListenerOptions(); + + this.handlerTouchStart = this.internalOnPointerDown.bind(this); + this.handlerTouchEnd = this.internalOnPointerEnd.bind(this); + this.handlerTouchMove = this.internalOnPointerMove.bind(this); + this.handlerTouchCancel = this.internalOnTouchCancel.bind(this); + + if (this.preventClick) { + this.handlerPreventClick = this.internalPreventClick.bind(this); + element.addEventListener("click", this.handlerPreventClick, options); + } + + if (SUPPORT_TOUCH) { + element.addEventListener("touchstart", this.handlerTouchStart, options); + element.addEventListener("touchend", this.handlerTouchEnd, options); + element.addEventListener("touchcancel", this.handlerTouchCancel, options); + } + + element.addEventListener("mousedown", this.handlerTouchStart, options); + element.addEventListener("mouseup", this.handlerTouchEnd, options); + element.addEventListener("mouseout", this.handlerTouchCancel, options); + + if (this.captureTouchmove) { + if (SUPPORT_TOUCH) { + element.addEventListener("touchmove", this.handlerTouchMove, options); + } + element.addEventListener("mousemove", this.handlerTouchMove, options); + } + + if (registerClickDetectors) { + window.activeClickDetectors.push(this); + } + this.element = element; + } + + /** + * Returns if the bound element is currently in the DOM. + */ + internalIsDomElementAttached() { + return this.element && document.documentElement.contains(this.element); + } + + /** + * Checks if the given event is relevant for this detector + * @param {TouchEvent|MouseEvent} event + */ + internalEventPreHandler(event, expectedRemainingTouches = 1) { + if (!this.element) { + // Already cleaned up + return false; + } + + if (this.targetOnly && event.target !== this.element) { + // Clicked a child element + return false; + } + + // Stop any propagation and defaults if configured + if (this.consumeEvents && event.cancelable) { + event.stopPropagation(); + } + + if (this.preventDefault && event.cancelable) { + event.preventDefault(); + } + + if (window.TouchEvent && event instanceof TouchEvent) { + clickDetectorGlobals.lastTouchTime = performance.now(); + + // console.log("Got touches", event.targetTouches.length, "vs", expectedRemainingTouches); + if (event.targetTouches.length !== expectedRemainingTouches) { + return false; + } + } + + if (event instanceof MouseEvent) { + if (performance.now() - clickDetectorGlobals.lastTouchTime < 1000.0) { + return false; + } + } + + return true; + } + + /** + * Extracts the mous position from an event + * @param {TouchEvent|MouseEvent} event + * @returns {Vector} The client space position + */ + static extractPointerPosition(event) { + if (window.TouchEvent && event instanceof TouchEvent) { + if (event.changedTouches.length !== 1) { + logger.warn( + "Got unexpected target touches:", + event.targetTouches.length, + "->", + event.targetTouches + ); + return new Vector(0, 0); + } + + const touch = event.changedTouches[0]; + return new Vector(touch.clientX, touch.clientY); + } + + if (event instanceof MouseEvent) { + return new Vector(event.clientX, event.clientY); + } + + assertAlways(false, "Got unknown event: " + event); + + return new Vector(0, 0); + } + + /** + * Cacnels all ongoing events on this detector + */ + cancelOngoingEvents() { + if (this.applyCssClass && this.element) { + this.element.classList.remove(this.applyCssClass); + } + this.clickDownPosition = null; + this.clickStartTime = null; + this.cancelled = true; + fastArrayDeleteValueIfContained(ongoingClickDetectors, this); + } + + /** + * Internal pointer down handler + * @param {TouchEvent|MouseEvent} event + */ + internalOnPointerDown(event) { + window.focus(); + + if (!this.internalEventPreHandler(event, 1)) { + return false; + } + + const position = /** @type {typeof ClickDetector} */ (this.constructor).extractPointerPosition(event); + + if (event instanceof MouseEvent) { + const isRightClick = event.button === 2; + if (isRightClick) { + // Ignore right clicks + this.rightClick.dispatch(position, event); + this.cancelled = true; + this.clickDownPosition = null; + return; + } + } + + if (this.clickDownPosition) { + logger.warn("Ignoring double click"); + return false; + } + + this.cancelled = false; + this.touchstart.dispatch(event); + + // Store where the touch started + this.clickDownPosition = position; + this.clickStartTime = performance.now(); + this.touchstartSimple.dispatch(this.clickDownPosition.x, this.clickDownPosition.y); + + // If we are not currently within a click, register it + if (ongoingClickDetectors.indexOf(this) < 0) { + ongoingClickDetectors.push(this); + } else { + logger.warn("Click detector got pointer down of active pointer twice"); + } + + // If we should apply any classes, do this now + if (this.applyCssClass) { + this.element.classList.add(this.applyCssClass); + } + + // If we should play any sound, do this + if (this.clickSound) { + GLOBAL_APP.sound.playUiSound(this.clickSound); + } + + return false; + } + + /** + * Internal pointer move handler + * @param {TouchEvent|MouseEvent} event + */ + internalOnPointerMove(event) { + if (!this.internalEventPreHandler(event, 1)) { + return false; + } + this.touchmove.dispatch(event); + const pos = /** @type {typeof ClickDetector} */ (this.constructor).extractPointerPosition(event); + this.touchmoveSimple.dispatch(pos.x, pos.y); + return false; + } + + /** + * Internal pointer end handler + * @param {TouchEvent|MouseEvent} event + */ + internalOnPointerEnd(event) { + window.focus(); + + if (!this.internalEventPreHandler(event, 0)) { + return false; + } + + if (this.cancelled) { + // warn(this, "Not dispatching touchend on cancelled listener"); + return false; + } + + if (event instanceof MouseEvent) { + const isRightClick = event.button === 2; + if (isRightClick) { + return; + } + } + + const index = ongoingClickDetectors.indexOf(this); + if (index < 0) { + logger.warn("Got pointer end but click detector is not in pressed state"); + } else { + fastArrayDelete(ongoingClickDetectors, index); + } + + let dispatchClick = false; + let dispatchClickPos = null; + + // Check for correct down position, otherwise must have pinched or so + if (this.clickDownPosition) { + const pos = /** @type {typeof ClickDetector} */ (this.constructor).extractPointerPosition(event); + const distance = pos.distance(this.clickDownPosition); + if (!IS_MOBILE || distance <= this.maxDistance) { + dispatchClick = true; + dispatchClickPos = pos; + } else { + console.warn("[ClickDetector] Touch does not count as click:", "(was", distance, ")"); + } + } + + this.clickDownPosition = null; + this.clickStartTime = null; + + if (this.applyCssClass) { + this.element.classList.remove(this.applyCssClass); + } + + // Dispatch in the end to avoid the element getting invalidated + // Also make sure that the element is still in the dom + if (this.internalIsDomElementAttached()) { + this.touchend.dispatch(event); + this.touchendSimple.dispatch(); + + if (dispatchClick) { + const detectors = ongoingClickDetectors.slice(); + for (let i = 0; i < detectors.length; ++i) { + detectors[i].cancelOngoingEvents(); + } + this.click.dispatch(dispatchClickPos, event); + } + } + return false; + } + + /** + * Internal touch cancel handler + * @param {TouchEvent|MouseEvent} event + */ + internalOnTouchCancel(event) { + if (!this.internalEventPreHandler(event, 0)) { + return false; + } + + if (this.cancelled) { + // warn(this, "Not dispatching touchcancel on cancelled listener"); + return false; + } + + this.cancelOngoingEvents(); + this.touchcancel.dispatch(event); + this.touchendSimple.dispatch(event); + return false; + } +} diff --git a/src/js/core/draw_parameters.js b/src/js/core/draw_parameters.js index 71971ed1..95e71bf2 100644 --- a/src/js/core/draw_parameters.js +++ b/src/js/core/draw_parameters.js @@ -1,26 +1,25 @@ -import { globalConfig } from "./config"; - -/** - * @typedef {import("../game/root").GameRoot} GameRoot - * @typedef {import("./rectangle").Rectangle} Rectangle - */ - -export class DrawParameters { - constructor({ context, visibleRect, desiredAtlasScale, zoomLevel, root }) { - /** @type {CanvasRenderingContext2D} */ - this.context = context; - - /** @type {Rectangle} */ - this.visibleRect = visibleRect; - - /** @type {string} */ - this.desiredAtlasScale = desiredAtlasScale; - - /** @type {number} */ - this.zoomLevel = zoomLevel; - - // FIXME: Not really nice - /** @type {GameRoot} */ - this.root = root; - } -} +import { globalConfig } from "./config"; + +/** + * @typedef {import("../game/root").GameRoot} GameRoot + * @typedef {import("./rectangle").Rectangle} Rectangle + */ + +export class DrawParameters { + constructor({ context, visibleRect, desiredAtlasScale, zoomLevel, root }) { + /** @type {CanvasRenderingContext2D} */ + this.context = context; + + /** @type {Rectangle} */ + this.visibleRect = visibleRect; + + /** @type {string} */ + this.desiredAtlasScale = desiredAtlasScale; + + /** @type {number} */ + this.zoomLevel = zoomLevel; + + /** @type {GameRoot} */ + this.root = root; + } +} diff --git a/src/js/core/restriction_manager.js b/src/js/core/restriction_manager.js index fb34acb8..2912d30f 100644 --- a/src/js/core/restriction_manager.js +++ b/src/js/core/restriction_manager.js @@ -44,7 +44,6 @@ export class RestrictionManager extends ReadWriteProxy { * @param {any} data */ migrate(data) { - // Todo return ExplainedResult.good(); } diff --git a/src/js/game/building_codes.js b/src/js/game/building_codes.js index 0a3cbc36..78240c31 100644 --- a/src/js/game/building_codes.js +++ b/src/js/game/building_codes.js @@ -41,7 +41,7 @@ const variantsCache = new Map(); export function registerBuildingVariant( code, meta, - variant = "default" /* FIXME: Circular dependency, actually its defaultBuildingVariant */, + variant = "default" /* @TODO: Circular dependency, actually its defaultBuildingVariant */, rotationVariant = 0 ) { assert(!gBuildingVariants[code], "Duplicate id: " + code); diff --git a/src/js/game/buildings/underground_belt.js b/src/js/game/buildings/underground_belt.js index fde018fe..2761443d 100644 --- a/src/js/game/buildings/underground_belt.js +++ b/src/js/game/buildings/underground_belt.js @@ -191,7 +191,6 @@ export class MetaUndergroundBeltBuilding extends MetaBuilding { ) { tile = tile.addScalars(searchVector.x, searchVector.y); - /* WIRES: FIXME */ const contents = root.map.getTileContent(tile, "regular"); if (contents) { const undergroundComp = contents.components.UndergroundBelt; diff --git a/src/js/game/entity_manager.js b/src/js/game/entity_manager.js index 613ed12d..b4101fc8 100644 --- a/src/js/game/entity_manager.js +++ b/src/js/game/entity_manager.js @@ -182,23 +182,6 @@ export class EntityManager extends BasicSerializableObject { return this.componentToEntity[componentHandle.getId()] || []; } - /** - * Return all of a given class. This is SLOW! - * @param {object} entityClass - * @returns {Array} entities - */ - getAllOfClass(entityClass) { - // FIXME: Slow - const result = []; - for (let i = 0; i < this.entities.length; ++i) { - const entity = this.entities[i]; - if (entity instanceof entityClass) { - result.push(entity); - } - } - return result; - } - /** * Unregisters all components of an entity from the component to entity mapping * @param {Entity} entity diff --git a/src/js/game/hud/parts/interactive_tutorial.js b/src/js/game/hud/parts/interactive_tutorial.js index cb0dde72..e48a77fb 100644 --- a/src/js/game/hud/parts/interactive_tutorial.js +++ b/src/js/game/hud/parts/interactive_tutorial.js @@ -11,6 +11,7 @@ import { ShapeItem } from "../../items/shape_item"; import { WireComponent } from "../../components/wire"; import { LeverComponent } from "../../components/lever"; +// @todo: Make dictionary const tutorialsByLevel = [ // Level 1 [ diff --git a/src/js/game/systems/item_ejector.js b/src/js/game/systems/item_ejector.js index 4f7d29be..56535111 100644 --- a/src/js/game/systems/item_ejector.js +++ b/src/js/game/systems/item_ejector.js @@ -225,8 +225,7 @@ export class ItemEjectorSystem extends GameSystemWithFilter { */ tryPassOverItem(item, receiver, slotIndex) { // Try figuring out how what to do with the item - // TODO: Kinda hacky. How to solve this properly? Don't want to go through inheritance hell. - // Also its just a few cases (hope it stays like this .. :x). + // @TODO: Kinda hacky. How to solve this properly? Don't want to go through inheritance hell. const beltComp = receiver.components.Belt; if (beltComp) { diff --git a/src/js/game/tutorial_goals_mappings.js b/src/js/game/tutorial_goals_mappings.js index 5cd966b9..e0d17cbc 100644 --- a/src/js/game/tutorial_goals_mappings.js +++ b/src/js/game/tutorial_goals_mappings.js @@ -55,7 +55,7 @@ export const enumHubGoalRewardsToContentUnlocked = { ]), [enumHubGoalRewards.reward_logic_gates]: typed([[MetaLogicGateBuilding, defaultBuildingVariant]]), [enumHubGoalRewards.reward_filter]: typed([[MetaFilterBuilding, defaultBuildingVariant]]), - [enumHubGoalRewards.reward_virtual_processing]: null, // @TODO! + [enumHubGoalRewards.reward_virtual_processing]: null, [enumHubGoalRewards.reward_wires_painter_and_levers]: typed([ [MetaPainterBuilding, enumPainterVariants.quad], diff --git a/src/js/savegame/savegame_manager.js b/src/js/savegame/savegame_manager.js index ed31dbcf..eb0d53d0 100644 --- a/src/js/savegame/savegame_manager.js +++ b/src/js/savegame/savegame_manager.js @@ -41,7 +41,7 @@ export class SavegameManager extends ReadWriteProxy { } verify(data) { - // TODO / FIXME!!!! + // @TODO return ExplainedResult.good(); } diff --git a/src/js/states/preload.js b/src/js/states/preload.js index c1746da6..811b9419 100644 --- a/src/js/states/preload.js +++ b/src/js/states/preload.js @@ -21,10 +21,6 @@ export class PreloadState extends GameState {
Booting - - - 0% -
@@ -56,10 +52,6 @@ export class PreloadState extends GameState { /** @type {HTMLElement} */ this.statusText = this.htmlElement.querySelector(".loadingStatus > .desc"); - /** @type {HTMLElement} */ - this.statusBar = this.htmlElement.querySelector(".loadingStatus > .bar > .inner"); - /** @type {HTMLElement} */ - this.statusBarText = this.htmlElement.querySelector(".loadingStatus > .bar > .status"); /** @type {HTMLElement} */ this.hintsText = this.htmlElement.querySelector(".prefab_GameHint"); @@ -67,7 +59,6 @@ export class PreloadState extends GameState { this.nextHintDuration = 0; this.currentStatus = "booting"; - this.currentIndex = 0; this.startLoading(); } @@ -259,16 +250,8 @@ export class PreloadState extends GameState { */ setStatus(text) { logger.log("✅ " + text); - this.currentIndex += 1; this.currentStatus = text; this.statusText.innerText = text; - - const numSteps = 10; // FIXME - - const percentage = (this.currentIndex / numSteps) * 100.0; - this.statusBar.style.width = percentage + "%"; - this.statusBarText.innerText = findNiceValue(percentage) + "%"; - return Promise.resolve(); } diff --git a/sync-translations.js b/sync-translations.js index 649daf1e..356a4aba 100644 --- a/sync-translations.js +++ b/sync-translations.js @@ -36,7 +36,7 @@ function match(originalObj, translatedObj, path = "/") { if (typeof valueOriginal === "object") { match(valueOriginal, valueMatching, path + key + "/"); } else if (typeof valueOriginal === "string") { - // todo + // @todo const originalPlaceholders = matchAll(valueOriginal, placeholderRegexp).toArray(); const translatedPlaceholders = matchAll(valueMatching, placeholderRegexp).toArray();