export class Reticle extends HTMLElement { render(_viewport, _windowSize) { throw new Error("Reticle.render must be overridden"); } } export class ReticleCursor extends Reticle { #container; constructor(nickname) { super(); this.nickname = nickname; } connectedCallback() { this.style.setProperty("--color", this.getColor()); this.#container = this.appendChild(document.createElement("div")); this.#container.classList.add("container"); this.classList.add("cursor"); let arrow = this.#container.appendChild(document.createElement("div")); arrow.classList.add("arrow"); let nickname = this.#container.appendChild(document.createElement("div")); nickname.classList.add("nickname"); nickname.textContent = this.nickname; } getColor() { let hash = 8803; for (let i = 0; i < this.nickname.length; ++i) { hash = (hash << 5) - hash + this.nickname.charCodeAt(i); hash |= 0; } return `oklch(65% 0.2 ${(hash / 0xffff) * 360}deg)`; } setCursor(x, y) { this.x = x; this.y = y; this.dispatchEvent(new Event(".update")); } render(viewport, windowSize) { let [viewportX, viewportY] = viewport.toScreenSpace(this.x, this.y, windowSize); this.style.transform = `translate(${viewportX}px, ${viewportY}px)`; } } customElements.define("rkgk-reticle-cursor", ReticleCursor); export class ReticleRenderer extends HTMLElement { #reticles = new Set(); #reticlesDiv; connectedCallback() { this.#reticlesDiv = this.appendChild(document.createElement("div")); this.#reticlesDiv.classList.add("reticles"); this.render(); let resizeObserver = new ResizeObserver(() => this.render()); resizeObserver.observe(this); } connectViewport(viewport) { this.viewport = viewport; this.render(); } addReticle(reticle) { if (!this.#reticles.has(reticle)) { reticle.addEventListener(".update", () => { if (this.viewport != null) { reticle.render(this.viewport, { width: this.clientWidth, height: this.clientHeight, }); } }); this.#reticles.add(reticle); this.#reticlesDiv.appendChild(reticle); } } removeReticle(reticle) { if (this.#reticles.has(reticle)) { this.#reticles.delete(reticle); this.#reticlesDiv.removeChild(reticle); } } render() { if (this.viewport == null) { console.debug("viewport is disconnected, skipping transform update"); return; } let windowSize = { width: this.clientWidth, height: this.clientHeight }; for (let reticle of this.#reticles.values()) { reticle.render(this.viewport, windowSize); } } } customElements.define("rkgk-reticle-renderer", ReticleRenderer);