@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
197 lines (155 loc) • 4.51 kB
JavaScript
import Rectangle from "../../core/geom/2d/Rectangle.js";
import AABB2 from "../../core/geom/2d/aabb/AABB2.js";
import { assert } from "../../core/assert.js";
/**
*
* @param {Element} el
* @param {number} depth
* @param {AABB2} aabb
*/
export function resizeAABB2ToFitBoundingClientRect(el, depth, aabb) {
/**
*
* @type {ClientRect | DOMRect}
*/
const rect = el.getBoundingClientRect();
if (rect.left < aabb.x0) {
aabb.x0 = rect.left;
}
if (rect.right > aabb.x1) {
aabb.x1 = rect.right;
}
if (rect.top < aabb.y0) {
aabb.y0 = rect.top;
}
if (rect.bottom > aabb.y1) {
aabb.y1 = rect.bottom;
}
if (depth > 0) {
const children = el.children;
for (let i = 0, l = children.length; i < l; i++) {
const child = children[i];
resizeAABB2ToFitBoundingClientRect(child, depth - 1, aabb);
}
}
}
const aabb = new AABB2();
/**
* Size observer of a DOM element
*/
export class DomSizeObserver {
/**
*
* @param {number} [depth=0] how deep should the observation go
*/
constructor({ depth = 0 } = {}) {
/**
*
* @type {Rectangle}
*/
this.dimensions = new Rectangle();
/**
*
* @type {number}
* @private
*/
this.__depth = depth;
/**
*
* @type {HTMLElement}
*/
this.element = null;
/**
* @private
* @type {boolean}
*/
this.running = false;
/**
*
* @type {ResizeObserver}
* @private
*/
this.__dom_resize_observer = new ResizeObserver(this.__dom_resize_observer_callback.bind(this));
}
/**
*
* @param {ResizeObserverEntry[]} entries
* @param {ResizeObserver} observer
* @private
*/
__dom_resize_observer_callback(entries, observer) {
const first_entry = entries[0];
/**
*
* @type {ClientRect | DOMRect}
*/
const rect = first_entry.contentRect;
aabb.setNegativelyInfiniteBounds();
resizeAABB2ToFitBoundingClientRect(this.element, this.__depth, aabb);
const rectangle = this.dimensions;
rectangle.position.set(aabb.x0, aabb.y0);
rectangle.size.set(rect.width, rect.height);
}
synchronize() {
aabb.setNegativelyInfiniteBounds();
resizeAABB2ToFitBoundingClientRect(this.element, this.__depth, aabb);
const rectangle = this.dimensions;
rectangle.position.set(aabb.x0, aabb.y0);
rectangle.size.set(aabb.getWidth(), aabb.getHeight());
}
start() {
if (this.running) {
// already running
return;
}
this.running = true;
// perform an immediate update
this.synchronize();
if (this.element !== null) {
this.__dom_resize_observer.observe(this.element);
}
}
/**
*
* @param {Element} element
*/
attach(element) {
this.element = element;
if (this.running) {
this.__dom_resize_observer.unobserve(this.element);
}
}
stop() {
if (!this.running) {
// not running
return;
}
this.running = false;
if (this.element !== null) {
this.__dom_resize_observer.unobserve(this.element);
}
}
/**
* Will attach to the view's element and automatically start/stop observing when the view is linked/unlinked
* @param {View} view
*/
watchView(view) {
assert.defined(view, 'view');
assert.notNull(view, 'view');
assert.equal(view.isView, true, 'view.isView !== true');
this.attach(view.el);
view.on.linked.add(this.start, this);
view.on.unlinked.add(this.stop, this);
}
/**
* Counterpart to {@link #watchView}
* @param {View} view
*/
unwatchView(view) {
assert.defined(view, 'view');
assert.notNull(view, 'view');
assert.equal(view.isView, true, 'view.isView !== true');
view.on.linked.remove(this.start, this);
view.on.unlinked.remove(this.stop, this);
}
}