|
|
@ -1,3 +1,4 @@
|
|
|
|
|
|
|
|
import { TrackedState } from "../../core/tracked_state";
|
|
|
|
import { GameRoot } from "../root";
|
|
|
|
import { GameRoot } from "../root";
|
|
|
|
|
|
|
|
|
|
|
|
// Automatically attaches and detaches elements from the dom
|
|
|
|
// Automatically attaches and detaches elements from the dom
|
|
|
@ -7,15 +8,28 @@ import { GameRoot } from "../root";
|
|
|
|
// Also attaches a class name if desired
|
|
|
|
// Also attaches a class name if desired
|
|
|
|
|
|
|
|
|
|
|
|
export class DynamicDomAttach {
|
|
|
|
export class DynamicDomAttach {
|
|
|
|
constructor(root, element, { timeToKeepSeconds = 0, attachClass = null } = {}) {
|
|
|
|
/**
|
|
|
|
|
|
|
|
*
|
|
|
|
|
|
|
|
* @param {GameRoot} root
|
|
|
|
|
|
|
|
* @param {HTMLElement} element
|
|
|
|
|
|
|
|
* @param {object} param2
|
|
|
|
|
|
|
|
* @param {number=} param2.timeToKeepSeconds How long to keep the element visible (in ms) after it should be hidden.
|
|
|
|
|
|
|
|
* Useful for fade-out effects
|
|
|
|
|
|
|
|
* @param {string=} param2.attachClass If set, attaches a class while the element is visible
|
|
|
|
|
|
|
|
* @param {boolean=} param2.trackHover If set, attaches the 'hovered' class if the cursor is above the element. Useful
|
|
|
|
|
|
|
|
* for fading out the element if its below the cursor for example.
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
constructor(root, element, { timeToKeepSeconds = 0, attachClass = null, trackHover = false } = {}) {
|
|
|
|
/** @type {GameRoot} */
|
|
|
|
/** @type {GameRoot} */
|
|
|
|
this.root = root;
|
|
|
|
this.root = root;
|
|
|
|
|
|
|
|
|
|
|
|
/** @type {HTMLElement} */
|
|
|
|
/** @type {HTMLElement} */
|
|
|
|
this.element = element;
|
|
|
|
this.element = element;
|
|
|
|
this.parent = this.element.parentElement;
|
|
|
|
this.parent = this.element.parentElement;
|
|
|
|
|
|
|
|
assert(this.parent, "Dom attach created without parent");
|
|
|
|
|
|
|
|
|
|
|
|
this.attachClass = attachClass;
|
|
|
|
this.attachClass = attachClass;
|
|
|
|
|
|
|
|
this.trackHover = trackHover;
|
|
|
|
|
|
|
|
|
|
|
|
this.timeToKeepSeconds = timeToKeepSeconds;
|
|
|
|
this.timeToKeepSeconds = timeToKeepSeconds;
|
|
|
|
this.lastVisibleTime = 0;
|
|
|
|
this.lastVisibleTime = 0;
|
|
|
@ -26,8 +40,19 @@ export class DynamicDomAttach {
|
|
|
|
|
|
|
|
|
|
|
|
this.internalIsClassAttached = false;
|
|
|
|
this.internalIsClassAttached = false;
|
|
|
|
this.classAttachTimeout = null;
|
|
|
|
this.classAttachTimeout = null;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Store the last bounds we computed
|
|
|
|
|
|
|
|
/** @type {DOMRect} */
|
|
|
|
|
|
|
|
this.lastComputedBounds = null;
|
|
|
|
|
|
|
|
this.lastComputedBoundsTime = -1;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Track the 'hovered' class
|
|
|
|
|
|
|
|
this.trackedIsHovered = new TrackedState(this.setIsHoveredClass, this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* Internal method to attach the element
|
|
|
|
|
|
|
|
*/
|
|
|
|
internalAttach() {
|
|
|
|
internalAttach() {
|
|
|
|
if (!this.attached) {
|
|
|
|
if (!this.attached) {
|
|
|
|
this.parent.appendChild(this.element);
|
|
|
|
this.parent.appendChild(this.element);
|
|
|
@ -36,6 +61,9 @@ export class DynamicDomAttach {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* Internal method to detach the element
|
|
|
|
|
|
|
|
*/
|
|
|
|
internalDetach() {
|
|
|
|
internalDetach() {
|
|
|
|
if (this.attached) {
|
|
|
|
if (this.attached) {
|
|
|
|
assert(this.element.parentElement === this.parent, "Invalid parent #2");
|
|
|
|
assert(this.element.parentElement === this.parent, "Invalid parent #2");
|
|
|
@ -44,14 +72,50 @@ export class DynamicDomAttach {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* Returns whether the element is currently attached
|
|
|
|
|
|
|
|
*/
|
|
|
|
isAttached() {
|
|
|
|
isAttached() {
|
|
|
|
return this.attached;
|
|
|
|
return this.attached;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* Actually sets the 'hovered' class
|
|
|
|
|
|
|
|
* @param {boolean} isHovered
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
setIsHoveredClass(isHovered) {
|
|
|
|
|
|
|
|
this.element.classList.toggle("hovered", isHovered);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* Call this every frame, and the dom attach class will take care of
|
|
|
|
|
|
|
|
* everything else
|
|
|
|
|
|
|
|
* @param {boolean} isVisible Whether the element should currently be visible or not
|
|
|
|
|
|
|
|
*/
|
|
|
|
update(isVisible) {
|
|
|
|
update(isVisible) {
|
|
|
|
if (isVisible) {
|
|
|
|
if (isVisible) {
|
|
|
|
this.lastVisibleTime = this.root ? this.root.time.realtimeNow() : 0;
|
|
|
|
this.lastVisibleTime = this.root ? this.root.time.realtimeNow() : 0;
|
|
|
|
this.internalAttach();
|
|
|
|
this.internalAttach();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (this.trackHover && this.root) {
|
|
|
|
|
|
|
|
let bounds = this.lastComputedBounds;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Recompute bounds only once in a while
|
|
|
|
|
|
|
|
if (!bounds || this.root.time.realtimeNow() - this.lastComputedBoundsTime > 1.0) {
|
|
|
|
|
|
|
|
bounds = this.lastComputedBounds = this.element.getBoundingClientRect();
|
|
|
|
|
|
|
|
this.lastComputedBoundsTime = this.root.time.realtimeNow();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const mousePos = this.root.app.mousePosition;
|
|
|
|
|
|
|
|
if (mousePos) {
|
|
|
|
|
|
|
|
this.trackedIsHovered.set(
|
|
|
|
|
|
|
|
mousePos.x > bounds.left &&
|
|
|
|
|
|
|
|
mousePos.x < bounds.right &&
|
|
|
|
|
|
|
|
mousePos.y > bounds.top &&
|
|
|
|
|
|
|
|
mousePos.y < bounds.bottom
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
if (!this.root || this.root.time.realtimeNow() - this.lastVisibleTime >= this.timeToKeepSeconds) {
|
|
|
|
if (!this.root || this.root.time.realtimeNow() - this.lastVisibleTime >= this.timeToKeepSeconds) {
|
|
|
|
this.internalDetach();
|
|
|
|
this.internalDetach();
|
|
|
|